@ericsanchezok/meta-synergy 0.0.0-dev-202603260728
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/.turbo/turbo-typecheck.log +1 -0
- package/dist/meta-protocol/src/bash.d.ts +89 -0
- package/dist/meta-protocol/src/bash.js +40 -0
- package/dist/meta-protocol/src/client.d.ts +9 -0
- package/dist/meta-protocol/src/client.js +1 -0
- package/dist/meta-protocol/src/env.d.ts +16 -0
- package/dist/meta-protocol/src/env.js +17 -0
- package/dist/meta-protocol/src/envelope.d.ts +50 -0
- package/dist/meta-protocol/src/envelope.js +23 -0
- package/dist/meta-protocol/src/error.d.ts +39 -0
- package/dist/meta-protocol/src/error.js +24 -0
- package/dist/meta-protocol/src/host.d.ts +90 -0
- package/dist/meta-protocol/src/host.js +27 -0
- package/dist/meta-protocol/src/index.d.ts +7 -0
- package/dist/meta-protocol/src/index.js +7 -0
- package/dist/meta-protocol/src/process.d.ts +274 -0
- package/dist/meta-protocol/src/process.js +89 -0
- package/dist/meta-synergy/src/client/holos-client.d.ts +15 -0
- package/dist/meta-synergy/src/client/holos-client.js +35 -0
- package/dist/meta-synergy/src/exec/bash-runner.d.ts +7 -0
- package/dist/meta-synergy/src/exec/bash-runner.js +9 -0
- package/dist/meta-synergy/src/exec/process-registry.d.ts +11 -0
- package/dist/meta-synergy/src/exec/process-registry.js +597 -0
- package/dist/meta-synergy/src/host.d.ts +32 -0
- package/dist/meta-synergy/src/host.js +27 -0
- package/dist/meta-synergy/src/index.d.ts +8 -0
- package/dist/meta-synergy/src/index.js +8 -0
- package/dist/meta-synergy/src/platform.d.ts +25 -0
- package/dist/meta-synergy/src/platform.js +230 -0
- package/dist/meta-synergy/src/rpc/handler.d.ts +66 -0
- package/dist/meta-synergy/src/rpc/handler.js +60 -0
- package/dist/meta-synergy/src/rpc/schema.d.ts +163 -0
- package/dist/meta-synergy/src/rpc/schema.js +11 -0
- package/dist/meta-synergy/src/types.d.ts +14 -0
- package/dist/meta-synergy/src/types.js +1 -0
- package/package.json +30 -0
- package/script/publish.ts +32 -0
- package/src/client/holos-client.ts +49 -0
- package/src/exec/bash-runner.ts +10 -0
- package/src/exec/process-registry.ts +728 -0
- package/src/host.ts +39 -0
- package/src/index.ts +8 -0
- package/src/platform.ts +227 -0
- package/src/rpc/handler.ts +76 -0
- package/src/rpc/schema.ts +16 -0
- package/src/types.ts +17 -0
- package/test/rpc-handler.test.ts +76 -0
- package/tsconfig.json +23 -0
package/src/host.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { MetaProtocolEnv, MetaProtocolHost } from "@ericsanchezok/meta-protocol"
|
|
2
|
+
import { Platform } from "./platform"
|
|
3
|
+
|
|
4
|
+
export interface MetaSynergyHostOptions {
|
|
5
|
+
envID?: MetaProtocolEnv.EnvID
|
|
6
|
+
hostSessionID?: MetaProtocolEnv.HostSessionID
|
|
7
|
+
capabilities?: MetaProtocolHost.Capabilities
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export class MetaSynergyHost {
|
|
11
|
+
readonly envID?: MetaProtocolEnv.EnvID
|
|
12
|
+
readonly hostSessionID: MetaProtocolEnv.HostSessionID
|
|
13
|
+
readonly capabilities: MetaProtocolHost.Capabilities
|
|
14
|
+
|
|
15
|
+
constructor(options: MetaSynergyHostOptions = {}) {
|
|
16
|
+
this.envID = options.envID
|
|
17
|
+
this.hostSessionID = options.hostSessionID || crypto.randomUUID()
|
|
18
|
+
this.capabilities = options.capabilities || Platform.detectCapabilities()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
hello() {
|
|
22
|
+
if (!this.envID) {
|
|
23
|
+
throw new Error("MetaSynergyHost requires envID to emit host.hello")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
type: "host.hello" as const,
|
|
28
|
+
envID: this.envID,
|
|
29
|
+
hostSessionID: this.hostSessionID,
|
|
30
|
+
capabilities: this.capabilities,
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
assertEnv(envID: string) {
|
|
35
|
+
if (this.envID && envID !== this.envID) {
|
|
36
|
+
throw new Error(`env mismatch: host bound to ${this.envID}, received ${envID}`)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
package/src/index.ts
ADDED
package/src/platform.ts
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import os from "node:os"
|
|
2
|
+
import path from "node:path"
|
|
3
|
+
import process from "node:process"
|
|
4
|
+
import { spawn } from "node:child_process"
|
|
5
|
+
import { MetaProtocolHost } from "@ericsanchezok/meta-protocol"
|
|
6
|
+
|
|
7
|
+
const SIGKILL_TIMEOUT_MS = 200
|
|
8
|
+
const ESC = "\u001b"
|
|
9
|
+
|
|
10
|
+
export type ProcessEnv = Record<string, string | undefined>
|
|
11
|
+
export type ChildLike = { pid?: number; kill(signal?: number | NodeJS.Signals): boolean }
|
|
12
|
+
|
|
13
|
+
export namespace Platform {
|
|
14
|
+
export function runtime(): MetaProtocolHost.Runtime {
|
|
15
|
+
if (typeof process.versions?.bun === "string") return "bun"
|
|
16
|
+
if (typeof process.versions?.node === "string") return "node"
|
|
17
|
+
return "unknown"
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function defaultShell(): MetaProtocolHost.Shell {
|
|
21
|
+
if (process.platform === "win32") {
|
|
22
|
+
const comspec = (process.env.ComSpec || process.env.COMSPEC || "").toLowerCase()
|
|
23
|
+
if (comspec.includes("pwsh")) return "pwsh"
|
|
24
|
+
if (comspec.includes("powershell")) return "powershell"
|
|
25
|
+
return "cmd"
|
|
26
|
+
}
|
|
27
|
+
return "sh"
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function supportedShells(): MetaProtocolHost.Shell[] {
|
|
31
|
+
return process.platform === "win32" ? ["cmd", "powershell", "pwsh"] : ["sh"]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function detectCapabilities(): MetaProtocolHost.Capabilities {
|
|
35
|
+
return {
|
|
36
|
+
platform: process.platform,
|
|
37
|
+
arch: process.arch,
|
|
38
|
+
hostname: safeHostname(),
|
|
39
|
+
runtime: runtime(),
|
|
40
|
+
defaultShell: defaultShell(),
|
|
41
|
+
supportedShells: supportedShells(),
|
|
42
|
+
supportsPty: false,
|
|
43
|
+
supportsSendKeys: true,
|
|
44
|
+
supportsSoftKill: process.platform !== "win32",
|
|
45
|
+
supportsProcessGroups: process.platform !== "win32",
|
|
46
|
+
envCaseInsensitive: process.platform === "win32",
|
|
47
|
+
lineEndings: process.platform === "win32" ? "crlf" : "lf",
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function normalizeEnv(env: ProcessEnv): ProcessEnv {
|
|
52
|
+
if (process.platform !== "win32") {
|
|
53
|
+
return { ...env }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const result: ProcessEnv = {}
|
|
57
|
+
const entries = Object.entries(env).sort(([left], [right]) => left.localeCompare(right))
|
|
58
|
+
const seen = new Set<string>()
|
|
59
|
+
for (const [key, value] of entries) {
|
|
60
|
+
const upper = key.toUpperCase()
|
|
61
|
+
if (seen.has(upper) && key !== "Path") continue
|
|
62
|
+
seen.add(upper)
|
|
63
|
+
result[key === "PATH" ? "Path" : key] = value
|
|
64
|
+
}
|
|
65
|
+
return result
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function resolveShellLaunch(command: string): { shell: MetaProtocolHost.Shell; file: string; args: string[] } {
|
|
69
|
+
if (process.platform === "win32") {
|
|
70
|
+
return {
|
|
71
|
+
shell: "cmd",
|
|
72
|
+
file: process.env.ComSpec || process.env.COMSPEC || "cmd.exe",
|
|
73
|
+
args: ["/d", "/s", "/c", command],
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
shell: "sh",
|
|
79
|
+
file: "/bin/sh",
|
|
80
|
+
args: ["-c", command],
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function killTree(child: ChildLike, exited?: () => boolean): Promise<void> {
|
|
85
|
+
const pid = child.pid
|
|
86
|
+
if (!pid || exited?.()) return
|
|
87
|
+
|
|
88
|
+
if (process.platform === "win32") {
|
|
89
|
+
await new Promise<void>((resolve) => {
|
|
90
|
+
const killer = spawn("taskkill", ["/pid", String(pid), "/f", "/t"], {
|
|
91
|
+
stdio: "ignore",
|
|
92
|
+
windowsHide: true,
|
|
93
|
+
})
|
|
94
|
+
killer.once("exit", () => resolve())
|
|
95
|
+
killer.once("error", () => resolve())
|
|
96
|
+
})
|
|
97
|
+
return
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
process.kill(-pid, "SIGTERM")
|
|
102
|
+
await sleep(SIGKILL_TIMEOUT_MS)
|
|
103
|
+
if (!exited?.()) process.kill(-pid, "SIGKILL")
|
|
104
|
+
return
|
|
105
|
+
} catch {}
|
|
106
|
+
|
|
107
|
+
child.kill("SIGTERM")
|
|
108
|
+
await sleep(SIGKILL_TIMEOUT_MS)
|
|
109
|
+
if (!exited?.()) child.kill("SIGKILL")
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function encodeKeySequence(keys: string[]): { data: string; warnings: string[] } {
|
|
113
|
+
const warnings: string[] = []
|
|
114
|
+
let data = ""
|
|
115
|
+
for (const token of keys) {
|
|
116
|
+
data += encodeKeyToken(token, warnings)
|
|
117
|
+
}
|
|
118
|
+
return { data, warnings }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function resolveWorkdir(workdir?: string): string {
|
|
122
|
+
if (!workdir) return process.cwd()
|
|
123
|
+
if (path.isAbsolute(workdir)) return workdir
|
|
124
|
+
return path.resolve(process.cwd(), workdir)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function sleep(ms: number): Promise<void> {
|
|
128
|
+
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function encodeKeyToken(raw: string, warnings: string[]): string {
|
|
133
|
+
const token = raw.trim()
|
|
134
|
+
if (!token) return ""
|
|
135
|
+
if (token.length === 2 && token.startsWith("^")) {
|
|
136
|
+
const ctrl = toCtrlChar(token[1])
|
|
137
|
+
if (ctrl) return ctrl
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const parsed = parseModifiers(token)
|
|
141
|
+
const named = namedKey(parsed.base.toLowerCase())
|
|
142
|
+
if (named) {
|
|
143
|
+
return parsed.alt ? `${ESC}${named}` : named
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (parsed.base.length === 1) {
|
|
147
|
+
let value = parsed.shift && /[a-z]/.test(parsed.base) ? parsed.base.toUpperCase() : parsed.base
|
|
148
|
+
if (parsed.ctrl) value = toCtrlChar(value) || value
|
|
149
|
+
if (parsed.alt) value = `${ESC}${value}`
|
|
150
|
+
return value
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (parsed.hasModifiers) {
|
|
154
|
+
warnings.push(`Unknown key \"${parsed.base}\" for modifiers; sending literal.`)
|
|
155
|
+
}
|
|
156
|
+
return parsed.base
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function parseModifiers(token: string) {
|
|
160
|
+
let rest = token
|
|
161
|
+
let ctrl = false
|
|
162
|
+
let alt = false
|
|
163
|
+
let shift = false
|
|
164
|
+
let hasModifiers = false
|
|
165
|
+
|
|
166
|
+
while (rest.length > 2 && rest[1] === "-") {
|
|
167
|
+
const mod = rest[0].toLowerCase()
|
|
168
|
+
if (mod === "c") ctrl = true
|
|
169
|
+
else if (mod === "m") alt = true
|
|
170
|
+
else if (mod === "s") shift = true
|
|
171
|
+
else break
|
|
172
|
+
hasModifiers = true
|
|
173
|
+
rest = rest.slice(2)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { base: rest, ctrl, alt, shift, hasModifiers }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function namedKey(input: string): string | undefined {
|
|
180
|
+
const map = new Map<string, string>([
|
|
181
|
+
["enter", "\r"],
|
|
182
|
+
["return", "\r"],
|
|
183
|
+
["tab", "\t"],
|
|
184
|
+
["escape", ESC],
|
|
185
|
+
["esc", ESC],
|
|
186
|
+
["space", " "],
|
|
187
|
+
["backspace", process.platform === "win32" ? "\b" : "\u007f"],
|
|
188
|
+
["up", `${ESC}[A`],
|
|
189
|
+
["down", `${ESC}[B`],
|
|
190
|
+
["right", `${ESC}[C`],
|
|
191
|
+
["left", `${ESC}[D`],
|
|
192
|
+
["home", `${ESC}[1~`],
|
|
193
|
+
["end", `${ESC}[4~`],
|
|
194
|
+
["pageup", `${ESC}[5~`],
|
|
195
|
+
["pagedown", `${ESC}[6~`],
|
|
196
|
+
["insert", `${ESC}[2~`],
|
|
197
|
+
["delete", `${ESC}[3~`],
|
|
198
|
+
["f1", `${ESC}OP`],
|
|
199
|
+
["f2", `${ESC}OQ`],
|
|
200
|
+
["f3", `${ESC}OR`],
|
|
201
|
+
["f4", `${ESC}OS`],
|
|
202
|
+
["f5", `${ESC}[15~`],
|
|
203
|
+
["f6", `${ESC}[17~`],
|
|
204
|
+
["f7", `${ESC}[18~`],
|
|
205
|
+
["f8", `${ESC}[19~`],
|
|
206
|
+
["f9", `${ESC}[20~`],
|
|
207
|
+
["f10", `${ESC}[21~`],
|
|
208
|
+
["f11", `${ESC}[23~`],
|
|
209
|
+
["f12", `${ESC}[24~`],
|
|
210
|
+
])
|
|
211
|
+
return map.get(input)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function toCtrlChar(char: string): string | null {
|
|
215
|
+
if (char.length !== 1) return null
|
|
216
|
+
if (char === "?") return "\u007f"
|
|
217
|
+
const code = char.toUpperCase().charCodeAt(0)
|
|
218
|
+
return code >= 64 && code <= 95 ? String.fromCharCode(code & 0x1f) : null
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function safeHostname(): string | undefined {
|
|
222
|
+
try {
|
|
223
|
+
return os.hostname()
|
|
224
|
+
} catch {
|
|
225
|
+
return undefined
|
|
226
|
+
}
|
|
227
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { MetaProtocolEnvelope, MetaProtocolError } from "@ericsanchezok/meta-protocol"
|
|
2
|
+
import { ProcessRegistry } from "../exec/process-registry"
|
|
3
|
+
import { MetaSynergyHost, type MetaSynergyHostOptions } from "../host"
|
|
4
|
+
import { BashRunner } from "../exec/bash-runner"
|
|
5
|
+
import { RPCRequestSchema } from "./schema"
|
|
6
|
+
|
|
7
|
+
export class RPCHandler {
|
|
8
|
+
readonly host: MetaSynergyHost
|
|
9
|
+
readonly processRegistry: ProcessRegistry
|
|
10
|
+
readonly bashRunner: BashRunner
|
|
11
|
+
|
|
12
|
+
constructor(options: MetaSynergyHostOptions = {}) {
|
|
13
|
+
this.host = new MetaSynergyHost(options)
|
|
14
|
+
this.processRegistry = new ProcessRegistry(this.host)
|
|
15
|
+
this.bashRunner = new BashRunner(this.processRegistry)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async handle(input: unknown) {
|
|
19
|
+
try {
|
|
20
|
+
const request = RPCRequestSchema.parse(input)
|
|
21
|
+
this.host.assertEnv(request.envID)
|
|
22
|
+
|
|
23
|
+
if (request.tool === "bash") {
|
|
24
|
+
const result = await this.bashRunner.run(request.payload, request.envID)
|
|
25
|
+
return {
|
|
26
|
+
version: 1,
|
|
27
|
+
requestID: request.requestID,
|
|
28
|
+
ok: true,
|
|
29
|
+
result,
|
|
30
|
+
} as const
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (request.tool === "process") {
|
|
34
|
+
const result = await this.processRegistry.execute(request.payload, request.envID)
|
|
35
|
+
return {
|
|
36
|
+
version: 1,
|
|
37
|
+
requestID: request.requestID,
|
|
38
|
+
ok: true,
|
|
39
|
+
result,
|
|
40
|
+
} as const
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return errorResult(undefined, "unsupported_tool", "Unsupported tool")
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (isEnvelopeError(error)) {
|
|
46
|
+
return errorResult(error.requestID, error.code, error.message, error.details)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return errorResult(undefined, "host_internal_error", error instanceof Error ? error.message : String(error))
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function errorResult(
|
|
55
|
+
requestID: string | undefined,
|
|
56
|
+
code: MetaProtocolError.Code,
|
|
57
|
+
message: string,
|
|
58
|
+
details?: unknown,
|
|
59
|
+
): MetaProtocolEnvelope.ErrorResult {
|
|
60
|
+
return {
|
|
61
|
+
version: 1,
|
|
62
|
+
requestID: requestID || crypto.randomUUID(),
|
|
63
|
+
ok: false,
|
|
64
|
+
error: {
|
|
65
|
+
code,
|
|
66
|
+
message,
|
|
67
|
+
details,
|
|
68
|
+
},
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function isEnvelopeError(
|
|
73
|
+
error: unknown,
|
|
74
|
+
): error is { requestID?: string; code: MetaProtocolError.Code; message: string; details?: unknown } {
|
|
75
|
+
return typeof error === "object" && error !== null && "code" in error && "message" in error
|
|
76
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import z from "zod"
|
|
2
|
+
import { MetaProtocolBash, MetaProtocolEnvelope, MetaProtocolProcess } from "@ericsanchezok/meta-protocol"
|
|
3
|
+
|
|
4
|
+
export const RPCRequestSchema = z.discriminatedUnion("tool", [
|
|
5
|
+
MetaProtocolBash.ExecuteRequest,
|
|
6
|
+
MetaProtocolProcess.ExecuteRequest,
|
|
7
|
+
])
|
|
8
|
+
|
|
9
|
+
export const RPCResultSchema = z.discriminatedUnion("ok", [
|
|
10
|
+
MetaProtocolBash.ExecuteResult,
|
|
11
|
+
MetaProtocolProcess.ExecuteResult,
|
|
12
|
+
MetaProtocolEnvelope.ErrorResult,
|
|
13
|
+
])
|
|
14
|
+
|
|
15
|
+
export type RPCRequest = z.infer<typeof RPCRequestSchema>
|
|
16
|
+
export type RPCResult = z.infer<typeof RPCResultSchema>
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { MetaProtocolEnv, MetaProtocolHost } from "@ericsanchezok/meta-protocol"
|
|
2
|
+
|
|
3
|
+
export type EnvID = MetaProtocolEnv.EnvID
|
|
4
|
+
export type RequestID = string
|
|
5
|
+
|
|
6
|
+
export interface RemoteHostIdentity {
|
|
7
|
+
envID: EnvID
|
|
8
|
+
hostSessionID: MetaProtocolEnv.HostSessionID
|
|
9
|
+
capabilities: MetaProtocolHost.Capabilities
|
|
10
|
+
label?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface HostTransport {
|
|
14
|
+
connect(): Promise<void>
|
|
15
|
+
disconnect(): Promise<void>
|
|
16
|
+
send(message: unknown): Promise<void>
|
|
17
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test"
|
|
2
|
+
import { RPCHandler } from "../src/rpc/handler"
|
|
3
|
+
|
|
4
|
+
describe("meta-synergy rpc handler", () => {
|
|
5
|
+
test("bash background execution returns process id", async () => {
|
|
6
|
+
const handler = new RPCHandler({ envID: "env_test" })
|
|
7
|
+
const result = await handler.handle({
|
|
8
|
+
version: 1,
|
|
9
|
+
requestID: "req_1",
|
|
10
|
+
envID: "env_test",
|
|
11
|
+
tool: "bash",
|
|
12
|
+
action: "execute",
|
|
13
|
+
payload: {
|
|
14
|
+
command: "echo hello && sleep 1",
|
|
15
|
+
description: "background test",
|
|
16
|
+
background: true,
|
|
17
|
+
},
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
expect(result.ok).toBe(true)
|
|
21
|
+
if (!result.ok) return
|
|
22
|
+
const metadata = result.result.metadata as { processId?: string; background?: boolean }
|
|
23
|
+
expect(metadata.processId).toBeTruthy()
|
|
24
|
+
expect(metadata.background).toBe(true)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test("process list includes backgrounded process", async () => {
|
|
28
|
+
const handler = new RPCHandler({ envID: "env_test" })
|
|
29
|
+
const started = await handler.handle({
|
|
30
|
+
version: 1,
|
|
31
|
+
requestID: "req_2",
|
|
32
|
+
envID: "env_test",
|
|
33
|
+
tool: "bash",
|
|
34
|
+
action: "execute",
|
|
35
|
+
payload: {
|
|
36
|
+
command: "echo hello && sleep 1",
|
|
37
|
+
description: "background test",
|
|
38
|
+
background: true,
|
|
39
|
+
},
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
expect(started.ok).toBe(true)
|
|
43
|
+
if (!started.ok) return
|
|
44
|
+
|
|
45
|
+
const listed = await handler.handle({
|
|
46
|
+
version: 1,
|
|
47
|
+
requestID: "req_3",
|
|
48
|
+
envID: "env_test",
|
|
49
|
+
tool: "process",
|
|
50
|
+
action: "list",
|
|
51
|
+
payload: { action: "list" },
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
expect(listed.ok).toBe(true)
|
|
55
|
+
if (!listed.ok) return
|
|
56
|
+
const startedMetadata = started.result.metadata as { processId?: string }
|
|
57
|
+
const listedMetadata = listed.result.metadata as { processes?: Array<{ processId: string }> }
|
|
58
|
+
expect(listedMetadata.processes?.some((item) => item.processId === startedMetadata.processId)).toBe(true)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test("env mismatch returns error envelope", async () => {
|
|
62
|
+
const handler = new RPCHandler({ envID: "env_bound" })
|
|
63
|
+
const result = await handler.handle({
|
|
64
|
+
version: 1,
|
|
65
|
+
requestID: "req_4",
|
|
66
|
+
envID: "env_other",
|
|
67
|
+
tool: "process",
|
|
68
|
+
action: "list",
|
|
69
|
+
payload: { action: "list" },
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
expect(result.ok).toBe(false)
|
|
73
|
+
if (result.ok) return
|
|
74
|
+
expect(result.error.code).toBe("host_internal_error")
|
|
75
|
+
})
|
|
76
|
+
})
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "ESNext",
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"lib": ["ESNext", "DOM"],
|
|
8
|
+
"types": ["node"],
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"allowSyntheticDefaultImports": true,
|
|
11
|
+
"esModuleInterop": true,
|
|
12
|
+
"outDir": "dist",
|
|
13
|
+
"declaration": true,
|
|
14
|
+
"noEmit": false,
|
|
15
|
+
"strict": true,
|
|
16
|
+
"isolatedModules": true,
|
|
17
|
+
"baseUrl": ".",
|
|
18
|
+
"paths": {
|
|
19
|
+
"@ericsanchezok/meta-protocol": ["../meta-protocol/src/index.ts"]
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"include": ["src"]
|
|
23
|
+
}
|