@khorsheed/dsh-local-agent 0.1.0-rc.6

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.
Files changed (99) hide show
  1. package/README.en.md +181 -0
  2. package/README.i18n.yaml +2 -0
  3. package/README.md +181 -0
  4. package/cordis.patch.yml +21 -0
  5. package/lib/client.js +8832 -0
  6. package/lib/index.js +5281 -0
  7. package/lib/invariant.js +85 -0
  8. package/lib/member-bridge.js +298 -0
  9. package/lib/typert.host.d.ts +3 -0
  10. package/lib/typert.host.js +3137 -0
  11. package/lib/typert.remote-client.d.ts +67 -0
  12. package/lib/typert.remote-client.js +1971 -0
  13. package/lib/types/cli-version.d.ts +82 -0
  14. package/lib/types/cli-version.js +165 -0
  15. package/lib/types/client/AuthStatusDot.d.ts +17 -0
  16. package/lib/types/client/AuthStatusDot.js +9 -0
  17. package/lib/types/client/HarnessModelPicker.d.ts +17 -0
  18. package/lib/types/client/HarnessModelPicker.js +48 -0
  19. package/lib/types/client/LocalAgentRecordsAction.d.ts +41 -0
  20. package/lib/types/client/LocalAgentRecordsAction.js +74 -0
  21. package/lib/types/client/MemberComposer.d.ts +79 -0
  22. package/lib/types/client/MemberComposer.js +308 -0
  23. package/lib/types/client/MemberConfiguration.d.ts +9 -0
  24. package/lib/types/client/MemberConfiguration.js +46 -0
  25. package/lib/types/client/MemberInboxView.d.ts +12 -0
  26. package/lib/types/client/MemberInboxView.js +38 -0
  27. package/lib/types/client/MemberLiveNode.d.ts +14 -0
  28. package/lib/types/client/MemberLiveNode.js +74 -0
  29. package/lib/types/client/MemberPaintDiagnostics.d.ts +7 -0
  30. package/lib/types/client/MemberPaintDiagnostics.js +20 -0
  31. package/lib/types/client/ModelConfigurationFields.d.ts +15 -0
  32. package/lib/types/client/ModelConfigurationFields.js +23 -0
  33. package/lib/types/client/ProviderAuthBlock.d.ts +59 -0
  34. package/lib/types/client/ProviderAuthBlock.js +164 -0
  35. package/lib/types/client/auth-status.d.ts +28 -0
  36. package/lib/types/client/auth-status.js +56 -0
  37. package/lib/types/client/icons.d.ts +22 -0
  38. package/lib/types/client/icons.js +6 -0
  39. package/lib/types/client/index.d.ts +62 -0
  40. package/lib/types/client/index.js +135 -0
  41. package/lib/types/client/live-node.d.ts +14 -0
  42. package/lib/types/client/live-node.js +34 -0
  43. package/lib/types/client/live-output.d.ts +34 -0
  44. package/lib/types/client/live-output.js +94 -0
  45. package/lib/types/client/live-paint.d.ts +40 -0
  46. package/lib/types/client/live-paint.js +74 -0
  47. package/lib/types/client/locales.d.ts +120 -0
  48. package/lib/types/client/locales.js +227 -0
  49. package/lib/types/client/member-configuration.d.ts +41 -0
  50. package/lib/types/client/member-configuration.js +114 -0
  51. package/lib/types/client/member-dock.d.ts +94 -0
  52. package/lib/types/client/member-dock.js +102 -0
  53. package/lib/types/client/member-feed.d.ts +24 -0
  54. package/lib/types/client/member-feed.js +134 -0
  55. package/lib/types/client/request-id.d.ts +3 -0
  56. package/lib/types/client/request-id.js +6 -0
  57. package/lib/types/client/settings-scope.d.ts +74 -0
  58. package/lib/types/client/settings-scope.js +98 -0
  59. package/lib/types/client/use-configuration-menu.d.ts +10 -0
  60. package/lib/types/client/use-configuration-menu.js +61 -0
  61. package/lib/types/container.d.ts +69 -0
  62. package/lib/types/container.js +129 -0
  63. package/lib/types/env.d.ts +17 -0
  64. package/lib/types/env.js +89 -0
  65. package/lib/types/gateway.d.ts +162 -0
  66. package/lib/types/gateway.js +470 -0
  67. package/lib/types/index.d.ts +1463 -0
  68. package/lib/types/index.js +2704 -0
  69. package/lib/types/invariant.d.ts +16 -0
  70. package/lib/types/invariant.js +105 -0
  71. package/lib/types/live-flush.d.ts +21 -0
  72. package/lib/types/live-flush.js +59 -0
  73. package/lib/types/live-stream.d.ts +41 -0
  74. package/lib/types/live-stream.js +178 -0
  75. package/lib/types/member-bridge.d.ts +50 -0
  76. package/lib/types/member-bridge.js +216 -0
  77. package/lib/types/member-channel.d.ts +86 -0
  78. package/lib/types/member-channel.js +232 -0
  79. package/lib/types/member-control-storage.d.ts +10 -0
  80. package/lib/types/member-control-storage.js +100 -0
  81. package/lib/types/member-control.d.ts +62 -0
  82. package/lib/types/member-control.js +359 -0
  83. package/lib/types/member-controls.d.ts +23 -0
  84. package/lib/types/member-controls.js +143 -0
  85. package/lib/types/member-feed.d.ts +4 -0
  86. package/lib/types/member-feed.js +45 -0
  87. package/lib/types/member-inbox.d.ts +30 -0
  88. package/lib/types/member-inbox.js +225 -0
  89. package/lib/types/model-directory.d.ts +43 -0
  90. package/lib/types/model-directory.js +192 -0
  91. package/lib/types/prepared-members.d.ts +15 -0
  92. package/lib/types/prepared-members.js +61 -0
  93. package/lib/types/settings-face.d.ts +61 -0
  94. package/lib/types/settings-face.js +61 -0
  95. package/lib/types/types.d.ts +959 -0
  96. package/lib/types/types.js +24 -0
  97. package/locale/en.json +6 -0
  98. package/locale/zh.json +6 -0
  99. package/package.json +112 -0
@@ -0,0 +1,85 @@
1
+ //#region lib/types/invariant.js
2
+ /**
3
+ * Package-owned invariant companion for `@khorsheed/dsh-local-agent`.
4
+ * @module @khorsheed/dsh-local-agent/invariant
5
+ */
6
+ const PACKAGE_NAME = "@khorsheed/dsh-local-agent";
7
+ /** Cordis companion plugin name. */
8
+ const name = "local-agent-invariant";
9
+ /** Service required before the companion can reserve package ownership. */
10
+ const inject = ["invariants"];
11
+ const NAME_PATTERN = /^[a-z][a-z0-9-]*$/u;
12
+ /**
13
+ * Audit the harness registry lifecycle: a registered harness must carry a
14
+ * valid, unique command-prefix name, a home environment variable, and a
15
+ * records adapter, and a removed name must have been registered first.
16
+ */
17
+ function auditLifecycle(ctx, fail) {
18
+ const known = /* @__PURE__ */ new Set();
19
+ ctx.on("internal/dispatch", (_mode, eventName, args) => {
20
+ if (eventName === "localAgent/harness-added") {
21
+ const harness = args[0];
22
+ if (!NAME_PATTERN.test(harness.name)) fail(`localAgent/harness-added names a harness with invalid name ${JSON.stringify(harness.name)}`);
23
+ if (known.has(harness.name)) fail(`localAgent/harness-added repeats name ${JSON.stringify(harness.name)}`);
24
+ if (harness.homeEnvVar.length === 0) fail(`localAgent/harness-added for ${harness.name} has an empty homeEnvVar`);
25
+ known.add(harness.name);
26
+ return;
27
+ }
28
+ if (eventName !== "localAgent/harness-removed") return;
29
+ const name = args[0];
30
+ if (!known.delete(name)) fail(`localAgent/harness-removed names unknown harness ${JSON.stringify(name)}`);
31
+ }, { global: true });
32
+ }
33
+ /**
34
+ * Cross-check delegation claims against the mounted subagent providers after
35
+ * the composition settles. A harness whose {@link LocalAgentHarness.delegationProvider}
36
+ * names a provider the tree does not mount means "identity present, delegation
37
+ * 404" — a typo in either the record or the bundle patch — and fails loud.
38
+ * A composition without the subagent seam (record-and-login only) skips the
39
+ * check; so does a harness without a delegation provider.
40
+ */
41
+ async function auditDelegationClaims(ctx, fail) {
42
+ await ctx.get("loader")?.await();
43
+ const subagents = ctx.get("subagents");
44
+ if (subagents === void 0) return;
45
+ const registry = ctx.get("localAgent");
46
+ if (registry === void 0) return;
47
+ for (const name of registry.list()) {
48
+ const providerName = registry.get(name)?.delegationProvider;
49
+ if (providerName === void 0) continue;
50
+ if (subagents.getProvider(providerName) === void 0) fail(`local-agent harness ${name} declares delegation provider ${JSON.stringify(providerName)} which is not mounted`);
51
+ }
52
+ }
53
+ /**
54
+ * Audit the delegation registry: a recorded delegation must name a mounted
55
+ * subagent provider (the record is only useful when a resume round can route
56
+ * back through the same provider). Mirrors the harness-level delegation-claim
57
+ * check with the same "identity present, delegation 404" rationale — a record
58
+ * whose provider the tree does not mount means a provider rename left a stale
59
+ * mapping that would fail loud on the first resume attempt.
60
+ */
61
+ async function auditDelegationProviders(ctx, fail) {
62
+ await ctx.get("loader")?.await();
63
+ const subagents = ctx.get("subagents");
64
+ if (subagents === void 0) return;
65
+ const registry = ctx.get("localAgent");
66
+ if (registry === void 0) return;
67
+ for (const record of registry.listDelegations()) if (subagents.getProvider(record.provider) === void 0) fail(`local-agent delegation for child session ${record.childSessionId} records provider ${JSON.stringify(record.provider)} which is not mounted`);
68
+ }
69
+ /**
70
+ * Install the package's checks.
71
+ * @param ctx - child context owned by this invariant registration.
72
+ * @param fail - reporter bound to the registering package name.
73
+ */
74
+ const install = Object.assign((ctx, fail) => {
75
+ auditLifecycle(ctx, fail);
76
+ return Promise.all([auditDelegationClaims(ctx, fail), auditDelegationProviders(ctx, fail)]).then(() => {});
77
+ }, {});
78
+ /**
79
+ * Register this package's invariant companion.
80
+ * @param ctx - plugin context carrying the invariant registry.
81
+ * @returns the installed registration's disposer.
82
+ */
83
+ const apply = (ctx) => Promise.resolve(ctx.invariants.register(PACKAGE_NAME, install));
84
+ //#endregion
85
+ export { apply, inject, name };
@@ -0,0 +1,298 @@
1
+ import { realpathSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ import { connect } from "node:net";
4
+ import { createInterface } from "node:readline";
5
+ //#region lib/types/types.js
6
+ /** Environment variable carrying the member-bridge socket path (spawn env → bridge). */
7
+ const MEMBER_BRIDGE_SOCKET_ENV = "DSH_MEMBER_SOCKET";
8
+ /** Environment variable carrying the per-run member-bridge token. */
9
+ const MEMBER_BRIDGE_TOKEN_ENV = "DSH_MEMBER_TOKEN";
10
+ //#endregion
11
+ //#region lib/types/member-bridge.js
12
+ /**
13
+ * Member bridge: a minimal standalone MCP server (stdio, newline-delimited
14
+ * JSON-RPC) exposing member messaging and room coordination tools to the CLI
15
+ * member that spawned it. Each call is forwarded to the host's member-channel
16
+ * listener over the loopback socket named by `DSH_MEMBER_SOCKET`,
17
+ * authenticated by the per-run token in `DSH_MEMBER_TOKEN`; the host's
18
+ * receipt is the tool result verbatim.
19
+ *
20
+ * The process is dependency-free (node builtins only) because the CLI spawns
21
+ * it bare: `node <this file>`, with the socket path and token in its
22
+ * environment (written into the CLI's scoped MCP config by the provider).
23
+ * Covers the MCP handshake subset a client needs: `initialize`,
24
+ * `notifications/initialized`, `ping`, `tools/list`, `tools/call`.
25
+ * @module @khorsheed/dsh-local-agent/member-bridge
26
+ */
27
+ /** The one tool this server exposes. */
28
+ const MEMBER_MESSAGE_TOOL = "member_message";
29
+ const TOOL_DESCRIPTION = [
30
+ "Notify another CLI member of this room/session group (member-to-member message).",
31
+ "`to` is the target member's dsh child session id (or a member name when a room manages the group).",
32
+ "The delivery receipt is returned verbatim: sent / pending-confirm / busy / an error reason."
33
+ ].join(" ");
34
+ const TOOL_SCHEMA = {
35
+ type: "object",
36
+ properties: {
37
+ to: {
38
+ type: "string",
39
+ description: "The target member's child session id (or room-roster name)."
40
+ },
41
+ text: {
42
+ type: "string",
43
+ description: "The notification text, delivered with your provenance."
44
+ }
45
+ },
46
+ required: ["to", "text"],
47
+ additionalProperties: false
48
+ };
49
+ const ROOM_TOOLS = [
50
+ {
51
+ name: "room_plan",
52
+ description: "Manage a formal goal with stages, dependent tasks and evidence review. Read room_read for plan state and the command contract. Ordinary small work uses room_message. Workers may submit only their own current attempt.",
53
+ inputSchema: {
54
+ type: "object",
55
+ properties: { command: {
56
+ type: "string",
57
+ description: "JSON command with action, requestId and expectedRevision; see room_read planCommands."
58
+ } },
59
+ required: ["command"],
60
+ additionalProperties: false
61
+ }
62
+ },
63
+ {
64
+ name: "room_read",
65
+ description: "Read your room roster, coordinator, deliveries, recent outcomes and available harness providers. Identity comes from the host.",
66
+ inputSchema: {
67
+ type: "object",
68
+ properties: {},
69
+ additionalProperties: false
70
+ }
71
+ },
72
+ {
73
+ name: "room_invite",
74
+ description: "Coordinator only: invite a member. An optional firstTask starts in the background and returns its result to you automatically.",
75
+ inputSchema: {
76
+ type: "object",
77
+ properties: {
78
+ provider: { type: "string" },
79
+ name: { type: "string" },
80
+ instructions: { type: "string" },
81
+ cwd: { type: "string" },
82
+ model: { type: "string" },
83
+ firstTask: { type: "string" }
84
+ },
85
+ required: ["provider", "name"],
86
+ additionalProperties: false
87
+ }
88
+ },
89
+ {
90
+ name: "room_message",
91
+ description: "Coordinator only: dispatch background work to a room member by name. Returns acceptance immediately; completion arrives as a correlated report. Do not poll.",
92
+ inputSchema: {
93
+ type: "object",
94
+ properties: {
95
+ member: { type: "string" },
96
+ text: { type: "string" }
97
+ },
98
+ required: ["member", "text"],
99
+ additionalProperties: false
100
+ }
101
+ }
102
+ ];
103
+ async function callRoomCommand(env, name, args) {
104
+ if (env.socket === void 0 || env.token === void 0) return toolText("member channel is not configured for this run", true);
105
+ if (args === null || typeof args !== "object" || Array.isArray(args)) return toolText("Room tool arguments must be an object", true);
106
+ try {
107
+ const outcome = await callHost(env.socket, {
108
+ token: env.token,
109
+ command: {
110
+ name,
111
+ arguments: args
112
+ }
113
+ });
114
+ return toolText(outcome.ok ? outcome.receipt ?? "accepted" : outcome.error ?? "Room command rejected", !outcome.ok);
115
+ } catch (error) {
116
+ return toolText(`Room command unavailable: ${String(error)}`, true);
117
+ }
118
+ }
119
+ /** One socket round trip: send the request line, read the one-line response. */
120
+ function callHost(socket, payload) {
121
+ return new Promise((resolve, reject) => {
122
+ const connection = connect(socket);
123
+ let buffer = "";
124
+ connection.on("connect", () => {
125
+ connection.write(`${JSON.stringify(payload)}\n`);
126
+ });
127
+ connection.on("data", (chunk) => {
128
+ buffer += chunk.toString();
129
+ const newline = buffer.indexOf("\n");
130
+ if (newline < 0) return;
131
+ connection.end();
132
+ try {
133
+ resolve(JSON.parse(buffer.slice(0, newline)));
134
+ } catch (error) {
135
+ reject(error instanceof Error ? error : new Error(String(error)));
136
+ }
137
+ });
138
+ connection.on("error", reject);
139
+ });
140
+ }
141
+ /** A successful MCP tool result carrying the receipt text. */
142
+ function toolText(text, isError = false) {
143
+ return {
144
+ content: [{
145
+ type: "text",
146
+ text
147
+ }],
148
+ isError
149
+ };
150
+ }
151
+ /**
152
+ * Handle one `member_message` tool call: forward to the host listener and map
153
+ * the outcome to a tool result. Channel failures (missing env, unreachable
154
+ * listener, rejected token) are tool errors; delivery verdicts are text.
155
+ * @param env - the bridge's environment (socket, token).
156
+ * @param args - the tool arguments (`to`, `text`).
157
+ * @returns the MCP tool result object.
158
+ */
159
+ async function callMemberMessage(env, args) {
160
+ if (env.socket === void 0 || env.token === void 0) return toolText("member channel is not configured for this run (missing socket/token environment)", true);
161
+ if (typeof args.to !== "string" || typeof args.text !== "string") return toolText("member_message requires string arguments `to` and `text`", true);
162
+ try {
163
+ const outcome = await callHost(env.socket, {
164
+ token: env.token,
165
+ to: args.to,
166
+ text: args.text
167
+ });
168
+ if (outcome.ok) return toolText(outcome.receipt ?? "sent");
169
+ return toolText(outcome.error ?? "member channel rejected the message", true);
170
+ } catch (error) {
171
+ return toolText(`member channel unavailable: ${error instanceof Error ? error.message : String(error)}`, true);
172
+ }
173
+ }
174
+ /**
175
+ * Run the stdio JSON-RPC loop over the given streams (injectable for tests).
176
+ * Unknown methods with an id get a MethodNotFound error; notifications are
177
+ * ignored. Never throws — a malformed line is answered with a parse error and
178
+ * the loop continues.
179
+ * @param env - the bridge's environment.
180
+ * @param input - the stdin side.
181
+ * @param output - the stdout side.
182
+ */
183
+ function serveMemberBridge(env, input, output) {
184
+ const write = (message) => {
185
+ output.write(`${JSON.stringify(message)}\n`);
186
+ };
187
+ createInterface({
188
+ input,
189
+ terminal: false
190
+ }).on("line", (line) => {
191
+ if (line.trim() === "") return;
192
+ let request;
193
+ try {
194
+ request = JSON.parse(line);
195
+ } catch {
196
+ write({
197
+ jsonrpc: "2.0",
198
+ id: null,
199
+ error: {
200
+ code: -32700,
201
+ message: "parse error"
202
+ }
203
+ });
204
+ return;
205
+ }
206
+ const id = request.id;
207
+ switch (request.method) {
208
+ case "initialize": {
209
+ const params = request.params;
210
+ write({
211
+ jsonrpc: "2.0",
212
+ id: id ?? null,
213
+ result: {
214
+ protocolVersion: params?.protocolVersion ?? "2024-11-05",
215
+ capabilities: { tools: {} },
216
+ serverInfo: {
217
+ name: "dsh-member-bridge",
218
+ version: "0.1.0"
219
+ }
220
+ }
221
+ });
222
+ return;
223
+ }
224
+ case "ping":
225
+ write({
226
+ jsonrpc: "2.0",
227
+ id: id ?? null,
228
+ result: {}
229
+ });
230
+ return;
231
+ case "tools/list":
232
+ write({
233
+ jsonrpc: "2.0",
234
+ id: id ?? null,
235
+ result: { tools: [{
236
+ name: MEMBER_MESSAGE_TOOL,
237
+ description: TOOL_DESCRIPTION,
238
+ inputSchema: TOOL_SCHEMA
239
+ }, ...ROOM_TOOLS] }
240
+ });
241
+ return;
242
+ case "tools/call": {
243
+ const params = request.params;
244
+ if (ROOM_TOOLS.some((tool) => tool.name === params?.name)) {
245
+ callRoomCommand(env, params.name, params.arguments ?? {}).then((result) => write({
246
+ jsonrpc: "2.0",
247
+ id: id ?? null,
248
+ result
249
+ }));
250
+ return;
251
+ }
252
+ if (params?.name !== "member_message") {
253
+ write({
254
+ jsonrpc: "2.0",
255
+ id: id ?? null,
256
+ error: {
257
+ code: -32602,
258
+ message: `unknown tool ${String(params?.name)}`
259
+ }
260
+ });
261
+ return;
262
+ }
263
+ callMemberMessage(env, params.arguments ?? {}).then((result) => {
264
+ write({
265
+ jsonrpc: "2.0",
266
+ id: id ?? null,
267
+ result
268
+ });
269
+ });
270
+ return;
271
+ }
272
+ default: if (id !== void 0) write({
273
+ jsonrpc: "2.0",
274
+ id,
275
+ error: {
276
+ code: -32601,
277
+ message: `method not found: ${String(request.method)}`
278
+ }
279
+ });
280
+ }
281
+ });
282
+ }
283
+ /** True when this file is the process entry (`node member-bridge.js`). */
284
+ function isMain() {
285
+ const entry = process.argv[1];
286
+ if (entry === void 0) return false;
287
+ try {
288
+ return realpathSync(entry) === realpathSync(fileURLToPath(import.meta.url));
289
+ } catch {
290
+ return false;
291
+ }
292
+ }
293
+ if (isMain()) serveMemberBridge({
294
+ socket: process.env[MEMBER_BRIDGE_SOCKET_ENV],
295
+ token: process.env[MEMBER_BRIDGE_TOKEN_ENV]
296
+ }, process.stdin, process.stdout);
297
+ //#endregion
298
+ export { MEMBER_MESSAGE_TOOL, callMemberMessage, callRoomCommand, serveMemberBridge };
@@ -0,0 +1,3 @@
1
+ /* Generated by @deepseek-ai/dsh-typert-generator from FaceModel — do not edit. */
2
+
3
+ export declare const TYPERT: unknown