@junwu168/openshell 0.1.3 → 0.1.4

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 (60) hide show
  1. package/dist/core/audit/log-store.js +1 -1
  2. package/dist/core/orchestrator.d.ts +2 -2
  3. package/dist/core/orchestrator.js +3 -3
  4. package/dist/core/result.d.ts +1 -1
  5. package/dist/index.d.ts +3 -3
  6. package/dist/index.js +3 -3
  7. package/dist/opencode/plugin.d.ts +1 -1
  8. package/dist/opencode/plugin.js +8 -8
  9. package/package.json +6 -1
  10. package/.claude/settings.local.json +0 -25
  11. package/bun.lock +0 -368
  12. package/docs/superpowers/notes/2026-03-25-opencode-remote-tools-handoff.md +0 -81
  13. package/docs/superpowers/notes/2026-03-26-openshell-pre-release-review.md +0 -174
  14. package/docs/superpowers/plans/2026-03-25-opencode-remote-tools.md +0 -1656
  15. package/docs/superpowers/plans/2026-03-25-server-registry-cli.md +0 -54
  16. package/docs/superpowers/plans/2026-03-26-config-backed-credential-registry.md +0 -494
  17. package/docs/superpowers/plans/2026-03-26-openshell-release-prep.md +0 -639
  18. package/docs/superpowers/specs/2026-03-25-opencode-remote-tools-design.md +0 -378
  19. package/docs/superpowers/specs/2026-03-26-config-backed-credential-registry-design.md +0 -272
  20. package/docs/superpowers/specs/2026-03-26-openshell-release-prep-design.md +0 -197
  21. package/examples/opencode-local/opencode.json +0 -19
  22. package/scripts/openshell.ts +0 -3
  23. package/scripts/server-registry.ts +0 -3
  24. package/src/cli/openshell.ts +0 -65
  25. package/src/cli/server-registry.ts +0 -476
  26. package/src/core/audit/git-audit-repo.ts +0 -42
  27. package/src/core/audit/log-store.ts +0 -20
  28. package/src/core/audit/redact.ts +0 -4
  29. package/src/core/contracts.ts +0 -51
  30. package/src/core/orchestrator.ts +0 -1082
  31. package/src/core/patch.ts +0 -11
  32. package/src/core/paths.ts +0 -32
  33. package/src/core/policy.ts +0 -30
  34. package/src/core/registry/server-registry.ts +0 -505
  35. package/src/core/result.ts +0 -16
  36. package/src/core/ssh/ssh-runtime.ts +0 -355
  37. package/src/index.ts +0 -3
  38. package/src/opencode/plugin.ts +0 -242
  39. package/src/product/install.ts +0 -43
  40. package/src/product/opencode-config.ts +0 -118
  41. package/src/product/uninstall.ts +0 -47
  42. package/src/product/workspace-tracker.ts +0 -69
  43. package/tests/integration/fake-ssh-server.ts +0 -97
  44. package/tests/integration/install-lifecycle.test.ts +0 -85
  45. package/tests/integration/orchestrator.test.ts +0 -767
  46. package/tests/integration/ssh-runtime.test.ts +0 -122
  47. package/tests/unit/audit.test.ts +0 -221
  48. package/tests/unit/build-layout.test.ts +0 -28
  49. package/tests/unit/opencode-config.test.ts +0 -100
  50. package/tests/unit/opencode-plugin.test.ts +0 -358
  51. package/tests/unit/openshell-cli.test.ts +0 -60
  52. package/tests/unit/paths.test.ts +0 -64
  53. package/tests/unit/plugin-export.test.ts +0 -10
  54. package/tests/unit/policy.test.ts +0 -53
  55. package/tests/unit/release-docs.test.ts +0 -31
  56. package/tests/unit/result.test.ts +0 -28
  57. package/tests/unit/server-registry-cli.test.ts +0 -673
  58. package/tests/unit/server-registry.test.ts +0 -452
  59. package/tests/unit/workspace-tracker.test.ts +0 -57
  60. package/tsconfig.json +0 -14
@@ -1,1082 +0,0 @@
1
- import { readFileSync } from "node:fs"
2
- import { isAbsolute, resolve } from "node:path"
3
- import type { ConnectConfig } from "ssh2"
4
- import type { PolicyDecision, ToolPayload, ToolResult } from "./contracts"
5
- import { applyUnifiedPatch } from "./patch"
6
- import { classifyRemoteExec } from "./policy"
7
- import { errorResult, okResult, partialFailureResult } from "./result"
8
- import type { ResolvedServerRecord, ServerRecord, ServerRegistry } from "./registry/server-registry"
9
-
10
- type ExecResult = {
11
- stdout: string
12
- stderr: string
13
- exitCode: number
14
- }
15
-
16
- type SshRuntime = {
17
- exec(connection: ConnectConfig, command: string, options?: { cwd?: string; timeout?: number }): Promise<ExecResult>
18
- readFile(connection: ConnectConfig, path: string): Promise<string>
19
- writeFile(connection: ConnectConfig, path: string, content: string, mode?: number): Promise<void>
20
- listDir(connection: ConnectConfig, path: string, recursive?: boolean, limit?: number): Promise<
21
- { name: string; longname: string }[] | string[]
22
- >
23
- stat(connection: ConnectConfig, path: string): Promise<{
24
- size: number
25
- mode: number
26
- isFile: boolean
27
- isDirectory: boolean
28
- }>
29
- }
30
-
31
- type AuditEngine = {
32
- preflightLog(): Promise<void>
33
- appendLog(entry: Record<string, unknown>): Promise<void>
34
- preflightSnapshots?(): Promise<void>
35
- captureSnapshots?(input: { server: string; path: string; before: string; after: string }): Promise<void>
36
- }
37
-
38
- type PolicyEngine = {
39
- classifyRemoteExec(command: string): {
40
- decision: PolicyDecision
41
- reason: string
42
- }
43
- }
44
-
45
- type OrchestratorOptions = {
46
- registry: Pick<ServerRegistry, "list" | "resolve">
47
- ssh: SshRuntime
48
- audit: AuditEngine
49
- policy?: PolicyEngine
50
- }
51
-
52
- type RemoteExecInput = {
53
- server: string
54
- command: string
55
- cwd?: string
56
- timeout?: number
57
- }
58
-
59
- type RemoteReadFileInput = {
60
- server: string
61
- path: string
62
- offset?: number
63
- length?: number
64
- }
65
-
66
- type RemoteWriteFileInput = {
67
- server: string
68
- path: string
69
- content: string
70
- mode?: number
71
- }
72
-
73
- type RemotePatchFileInput = {
74
- server: string
75
- path: string
76
- patch: string
77
- }
78
-
79
- type RemoteListDirInput = {
80
- server: string
81
- path: string
82
- recursive?: boolean
83
- limit?: number
84
- }
85
-
86
- type RemoteStatInput = {
87
- server: string
88
- path: string
89
- }
90
-
91
- type RemoteFindInput = {
92
- server: string
93
- path: string
94
- pattern: string
95
- glob?: string
96
- limit?: number
97
- }
98
-
99
- const quoteShell = (value: string) => `'${value.replaceAll("'", `'\"'\"'`)}'`
100
-
101
- type AuthPathErrorCode = "AUTH_PATH_INVALID" | "KEY_PATH_NOT_FOUND" | "CERTIFICATE_PATH_NOT_FOUND" | "AUTH_PATH_UNREADABLE"
102
-
103
- const authError = <T>(
104
- tool: string,
105
- server: ResolvedServerRecord,
106
- code: AuthPathErrorCode,
107
- message: string,
108
- ): ToolResult<T> =>
109
- errorResult({
110
- tool,
111
- server: server.id,
112
- code,
113
- message,
114
- execution: { attempted: false, completed: false },
115
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
116
- })
117
-
118
- const resolveAuthPath = (
119
- server: ResolvedServerRecord,
120
- pathValue: string,
121
- ): { path: string } | { code: AuthPathErrorCode; message: string } => {
122
- if (isAbsolute(pathValue)) {
123
- return { path: pathValue }
124
- }
125
-
126
- if (server.scope !== "workspace" || !server.workspaceRoot) {
127
- return {
128
- code: "AUTH_PATH_INVALID",
129
- message: `Relative auth paths are only allowed for workspace-scoped records: ${pathValue}`,
130
- }
131
- }
132
-
133
- return {
134
- path: resolve(server.workspaceRoot, pathValue),
135
- }
136
- }
137
-
138
- const readAuthFile = (
139
- server: ResolvedServerRecord,
140
- tool: string,
141
- pathValue: string,
142
- missingCode: "KEY_PATH_NOT_FOUND" | "CERTIFICATE_PATH_NOT_FOUND",
143
- ): { content: string } | ToolResult<never> => {
144
- const resolved = resolveAuthPath(server, pathValue)
145
- if ("code" in resolved) {
146
- return authError(tool, server, resolved.code, resolved.message)
147
- }
148
-
149
- try {
150
- return { content: readFileSync(resolved.path, "utf8") }
151
- } catch (error) {
152
- const errno = (error as NodeJS.ErrnoException).code
153
- if (errno === "ENOENT") {
154
- return authError(tool, server, missingCode, `Auth file not found: ${resolved.path}`)
155
- }
156
-
157
- return authError(tool, server, "AUTH_PATH_UNREADABLE", `Auth file is unreadable: ${resolved.path}`)
158
- }
159
- }
160
-
161
- const toConnectConfig = (tool: string, server: ResolvedServerRecord): ConnectConfig | ToolResult<never> => {
162
- const base = {
163
- host: server.host,
164
- port: server.port,
165
- username: server.username,
166
- }
167
-
168
- switch (server.auth.kind) {
169
- case "password":
170
- return {
171
- ...base,
172
- password: server.auth.secret,
173
- }
174
- case "privateKey": {
175
- const privateKey = readAuthFile(server, tool, server.auth.privateKeyPath, "KEY_PATH_NOT_FOUND")
176
- if ("status" in privateKey) {
177
- return privateKey
178
- }
179
-
180
- return {
181
- ...base,
182
- privateKey: privateKey.content,
183
- ...(server.auth.passphrase ? { passphrase: server.auth.passphrase } : {}),
184
- }
185
- }
186
- case "certificate": {
187
- const certificate = readAuthFile(server, tool, server.auth.certificatePath, "CERTIFICATE_PATH_NOT_FOUND")
188
- if ("status" in certificate) {
189
- return certificate
190
- }
191
-
192
- const privateKey = readAuthFile(server, tool, server.auth.privateKeyPath, "KEY_PATH_NOT_FOUND")
193
- if ("status" in privateKey) {
194
- return privateKey
195
- }
196
-
197
- return {
198
- ...base,
199
- privateKey: privateKey.content,
200
- ...(server.auth.passphrase ? { passphrase: server.auth.passphrase } : {}),
201
- }
202
- }
203
- }
204
- }
205
-
206
- const withAuditFlag = <T>(
207
- status: "ok" | "partial_failure" | "error",
208
- payload: ToolPayload<T>,
209
- logWritten: boolean,
210
- ): ToolResult<T> => {
211
- const next: ToolPayload<T> = {
212
- ...payload,
213
- audit: {
214
- logWritten,
215
- snapshotStatus: payload.audit?.snapshotStatus ?? "not-applicable",
216
- },
217
- }
218
-
219
- if (status === "ok") {
220
- return okResult(next)
221
- }
222
-
223
- if (status === "partial_failure") {
224
- return partialFailureResult(next)
225
- }
226
-
227
- return errorResult(next)
228
- }
229
-
230
- const byteLength = (value: string) => Buffer.byteLength(value)
231
- const clampLimit = (value: number | undefined, fallback: number) => Math.max(1, Math.trunc(value ?? fallback))
232
-
233
- export const createOrchestrator = ({ registry, ssh, audit, policy = { classifyRemoteExec } }: OrchestratorOptions) => {
234
- const appendLogSafe = async (entry: Record<string, unknown>) => {
235
- try {
236
- await audit.appendLog(entry)
237
- return true
238
- } catch {
239
- return false
240
- }
241
- }
242
-
243
- const isRegistryValidationError = (
244
- error: unknown,
245
- ): error is { code: "REGISTRY_RECORD_INVALID"; message: string } =>
246
- typeof error === "object" &&
247
- error !== null &&
248
- (error as { code?: unknown }).code === "REGISTRY_RECORD_INVALID" &&
249
- typeof (error as { message?: unknown }).message === "string"
250
-
251
- const preflightLog = async <T>(tool: string, server?: string): Promise<ToolResult<T> | null> => {
252
- try {
253
- await audit.preflightLog()
254
- return null
255
- } catch (error) {
256
- return errorResult({
257
- tool,
258
- server,
259
- code: "AUDIT_LOG_PREFLIGHT_FAILED",
260
- message: (error as Error).message,
261
- execution: { attempted: false, completed: false },
262
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
263
- })
264
- }
265
- }
266
-
267
- const logAuthFailure = async <T>(
268
- tool: string,
269
- approvalStatus: string,
270
- logEntry: Record<string, unknown>,
271
- result: ToolResult<T>,
272
- ): Promise<ToolResult<T>> => {
273
- const logWritten = await appendLogSafe({
274
- ...logEntry,
275
- tool,
276
- server: result.server,
277
- approvalStatus,
278
- code: result.code,
279
- message: result.message,
280
- })
281
-
282
- return withAuditFlag("error", result, logWritten)
283
- }
284
-
285
- const resolveServer = async <T>(
286
- tool: string,
287
- serverId: string,
288
- logEntry: Record<string, unknown>,
289
- approvalStatus: string,
290
- ): Promise<{ result: ToolResult<T> | null; server: ResolvedServerRecord | null }> => {
291
- let server: ResolvedServerRecord | null
292
- try {
293
- server = await registry.resolve(serverId)
294
- } catch (error) {
295
- if (isRegistryValidationError(error)) {
296
- const payload: ToolPayload<T> = {
297
- tool,
298
- server: serverId,
299
- code: error.code,
300
- message: error.message,
301
- execution: { attempted: false, completed: false },
302
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
303
- }
304
-
305
- const logWritten = await appendLogSafe({
306
- ...logEntry,
307
- tool,
308
- server: serverId,
309
- approvalStatus,
310
- code: error.code,
311
- message: error.message,
312
- })
313
-
314
- return {
315
- result: withAuditFlag("error", payload, logWritten),
316
- server: null,
317
- }
318
- }
319
-
320
- const payload: ToolPayload<T> = {
321
- tool,
322
- server: serverId,
323
- code: "SERVER_RESOLVE_FAILED",
324
- message: (error as Error).message,
325
- execution: { attempted: false, completed: false },
326
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
327
- }
328
-
329
- const logWritten = await appendLogSafe({
330
- ...logEntry,
331
- tool,
332
- server: serverId,
333
- approvalStatus,
334
- code: "SERVER_RESOLVE_FAILED",
335
- message: payload.message,
336
- })
337
-
338
- return {
339
- result: withAuditFlag("error", payload, logWritten),
340
- server: null,
341
- }
342
- }
343
-
344
- if (!server) {
345
- const payload: ToolPayload<T> = {
346
- tool,
347
- server: serverId,
348
- code: "SERVER_NOT_FOUND",
349
- execution: { attempted: false, completed: false },
350
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
351
- }
352
-
353
- const logWritten = await appendLogSafe({
354
- ...logEntry,
355
- tool,
356
- server: serverId,
357
- approvalStatus,
358
- code: "SERVER_NOT_FOUND",
359
- })
360
-
361
- return {
362
- result: withAuditFlag("error", payload, logWritten),
363
- server: null,
364
- }
365
- }
366
-
367
- return {
368
- result: null,
369
- server,
370
- }
371
- }
372
-
373
- const listServers = async (): Promise<ToolResult<Array<Omit<ServerRecord, "auth">>>> => {
374
- const logReady = await preflightLog<Array<Omit<ServerRecord, "auth">>>("list_servers")
375
- if (logReady) {
376
- return logReady
377
- }
378
-
379
- try {
380
- const servers = await registry.list()
381
- const data = servers.map(({ auth: _auth, workspaceRoot: _workspaceRoot, ...server }) => server)
382
- const payload: ToolPayload<Array<Omit<ServerRecord, "auth">>> = {
383
- tool: "list_servers",
384
- data,
385
- execution: { attempted: true, completed: true },
386
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
387
- }
388
- const logWritten = await appendLogSafe({
389
- tool: "list_servers",
390
- approvalStatus: "not-required",
391
- count: data.length,
392
- })
393
- return withAuditFlag("ok", payload, logWritten)
394
- } catch (error) {
395
- if (isRegistryValidationError(error)) {
396
- const payload: ToolPayload<Array<Omit<ServerRecord, "auth">>> = {
397
- tool: "list_servers",
398
- code: error.code,
399
- message: error.message,
400
- execution: { attempted: false, completed: false },
401
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
402
- }
403
- const logWritten = await appendLogSafe({
404
- tool: "list_servers",
405
- approvalStatus: "not-required",
406
- code: error.code,
407
- message: payload.message,
408
- })
409
- return withAuditFlag("error", payload, logWritten)
410
- }
411
-
412
- const payload: ToolPayload<Array<Omit<ServerRecord, "auth">>> = {
413
- tool: "list_servers",
414
- code: "REGISTRY_LIST_FAILED",
415
- message: (error as Error).message,
416
- execution: { attempted: false, completed: false },
417
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
418
- }
419
- const logWritten = await appendLogSafe({
420
- tool: "list_servers",
421
- approvalStatus: "not-required",
422
- code: "REGISTRY_LIST_FAILED",
423
- message: payload.message,
424
- })
425
- return withAuditFlag("error", payload, logWritten)
426
- }
427
- }
428
-
429
- const remoteExec = async (input: RemoteExecInput): Promise<ToolResult<ExecResult>> => {
430
- const logReady = await preflightLog<ExecResult>("remote_exec", input.server)
431
- if (logReady) {
432
- return logReady
433
- }
434
-
435
- const classification = policy.classifyRemoteExec(input.command)
436
- const approvalStatus =
437
- classification.decision === "approval-required" ? "host-managed-required" : "not-required"
438
-
439
- const resolved = await resolveServer(
440
- "remote_exec",
441
- input.server,
442
- { command: input.command, cwd: input.cwd, timeout: input.timeout },
443
- "unknown",
444
- )
445
- if (resolved.result) {
446
- return resolved.result as ToolResult<ExecResult>
447
- }
448
-
449
- if (classification.decision === "reject") {
450
- const payload: ToolPayload<ExecResult> = {
451
- tool: "remote_exec",
452
- server: input.server,
453
- code: "POLICY_REJECTED",
454
- message: classification.reason,
455
- execution: { attempted: false, completed: false },
456
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
457
- }
458
- const logWritten = await appendLogSafe({
459
- tool: "remote_exec",
460
- server: input.server,
461
- command: input.command,
462
- cwd: input.cwd,
463
- timeout: input.timeout,
464
- approvalStatus: "not-required",
465
- code: "POLICY_REJECTED",
466
- message: classification.reason,
467
- })
468
- return withAuditFlag("error", payload, logWritten)
469
- }
470
-
471
- try {
472
- const connection = toConnectConfig("remote_exec", resolved.server!)
473
- if ("status" in connection) {
474
- return logAuthFailure(
475
- "remote_exec",
476
- approvalStatus,
477
- { command: input.command, cwd: input.cwd, timeout: input.timeout },
478
- connection,
479
- )
480
- }
481
-
482
- const executed = await ssh.exec(connection, input.command, {
483
- cwd: input.cwd,
484
- timeout: input.timeout,
485
- })
486
- const payload: ToolPayload<ExecResult> = {
487
- tool: "remote_exec",
488
- server: input.server,
489
- data: executed,
490
- execution: {
491
- attempted: true,
492
- completed: true,
493
- exitCode: executed.exitCode,
494
- stdoutBytes: byteLength(executed.stdout),
495
- stderrBytes: byteLength(executed.stderr),
496
- },
497
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
498
- }
499
- const logWritten = await appendLogSafe({
500
- tool: "remote_exec",
501
- server: input.server,
502
- command: input.command,
503
- cwd: input.cwd,
504
- timeout: input.timeout,
505
- approvalStatus,
506
- policyDecision: classification.decision,
507
- approvalRequired: classification.decision === "approval-required",
508
- ...executed,
509
- })
510
- return withAuditFlag("ok", payload, logWritten)
511
- } catch (error) {
512
- const payload: ToolPayload<ExecResult> = {
513
- tool: "remote_exec",
514
- server: input.server,
515
- code: "SSH_EXEC_FAILED",
516
- message: (error as Error).message,
517
- execution: { attempted: true, completed: false },
518
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
519
- }
520
- const logWritten = await appendLogSafe({
521
- tool: "remote_exec",
522
- server: input.server,
523
- command: input.command,
524
- cwd: input.cwd,
525
- timeout: input.timeout,
526
- approvalStatus,
527
- code: "SSH_EXEC_FAILED",
528
- message: payload.message,
529
- })
530
- return withAuditFlag("error", payload, logWritten)
531
- }
532
- }
533
-
534
- const remoteReadFile = async (input: RemoteReadFileInput): Promise<ToolResult<{ content: string }>> => {
535
- const logReady = await preflightLog<{ content: string }>("remote_read_file", input.server)
536
- if (logReady) {
537
- return logReady
538
- }
539
-
540
- const resolved = await resolveServer(
541
- "remote_read_file",
542
- input.server,
543
- { path: input.path, offset: input.offset, length: input.length },
544
- "not-required",
545
- )
546
- if (resolved.result) {
547
- return resolved.result as ToolResult<{ content: string }>
548
- }
549
-
550
- try {
551
- const connection = toConnectConfig("remote_read_file", resolved.server!)
552
- if ("status" in connection) {
553
- return logAuthFailure(
554
- "remote_read_file",
555
- "not-required",
556
- { path: input.path, offset: input.offset, length: input.length },
557
- connection,
558
- )
559
- }
560
-
561
- const body = await ssh.readFile(connection, input.path)
562
- const offset = input.offset ?? 0
563
- const content = body.slice(offset, input.length ? offset + input.length : undefined)
564
- const payload: ToolPayload<{ content: string }> = {
565
- tool: "remote_read_file",
566
- server: input.server,
567
- data: { content },
568
- execution: { attempted: true, completed: true },
569
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
570
- }
571
- const logWritten = await appendLogSafe({
572
- tool: "remote_read_file",
573
- server: input.server,
574
- path: input.path,
575
- offset: input.offset,
576
- length: input.length,
577
- approvalStatus: "not-required",
578
- })
579
- return withAuditFlag("ok", payload, logWritten)
580
- } catch (error) {
581
- const payload: ToolPayload<{ content: string }> = {
582
- tool: "remote_read_file",
583
- server: input.server,
584
- code: "SSH_READ_FAILED",
585
- message: (error as Error).message,
586
- execution: { attempted: true, completed: false },
587
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
588
- }
589
- const logWritten = await appendLogSafe({
590
- tool: "remote_read_file",
591
- server: input.server,
592
- path: input.path,
593
- approvalStatus: "not-required",
594
- code: "SSH_READ_FAILED",
595
- message: payload.message,
596
- })
597
- return withAuditFlag("error", payload, logWritten)
598
- }
599
- }
600
-
601
- const remoteWriteFile = async (input: RemoteWriteFileInput): Promise<ToolResult> => {
602
- const logReady = await preflightLog("remote_write_file", input.server)
603
- if (logReady) {
604
- return logReady
605
- }
606
-
607
- const resolved = await resolveServer(
608
- "remote_write_file",
609
- input.server,
610
- { path: input.path, mode: input.mode },
611
- "host-managed-required",
612
- )
613
- if (resolved.result) {
614
- return resolved.result
615
- }
616
-
617
- const connection = toConnectConfig("remote_write_file", resolved.server!)
618
- if ("status" in connection) {
619
- return logAuthFailure("remote_write_file", "host-managed-required", { path: input.path, mode: input.mode }, connection)
620
- }
621
-
622
- try {
623
- await (audit.preflightSnapshots?.() ?? Promise.resolve())
624
- } catch (error) {
625
- const payload: ToolPayload<ExecResult> = {
626
- tool: "remote_write_file",
627
- server: input.server,
628
- code: "AUDIT_SNAPSHOT_PREFLIGHT_FAILED",
629
- message: (error as Error).message,
630
- execution: { attempted: false, completed: false },
631
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
632
- }
633
- const logWritten = await appendLogSafe({
634
- tool: "remote_write_file",
635
- server: input.server,
636
- path: input.path,
637
- mode: input.mode,
638
- approvalStatus: "host-managed-required",
639
- code: "AUDIT_SNAPSHOT_PREFLIGHT_FAILED",
640
- message: payload.message,
641
- })
642
- return withAuditFlag("error", payload, logWritten)
643
- }
644
-
645
- const before = await ssh.readFile(connection, input.path).catch(() => "")
646
- try {
647
- await ssh.writeFile(connection, input.path, input.content, input.mode)
648
- } catch (error) {
649
- const payload: ToolPayload<{ name: string; longname: string }[] | string[]> = {
650
- tool: "remote_write_file",
651
- server: input.server,
652
- code: "SSH_WRITE_FAILED",
653
- message: (error as Error).message,
654
- execution: { attempted: true, completed: false },
655
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
656
- }
657
- const logWritten = await appendLogSafe({
658
- tool: "remote_write_file",
659
- server: input.server,
660
- path: input.path,
661
- mode: input.mode,
662
- approvalStatus: "host-managed-required",
663
- approvalRequired: true,
664
- code: "SSH_WRITE_FAILED",
665
- message: payload.message,
666
- })
667
- return withAuditFlag("error", payload, logWritten)
668
- }
669
-
670
- const after = await ssh.readFile(connection, input.path).catch(() => input.content)
671
- const logWritten = await appendLogSafe({
672
- tool: "remote_write_file",
673
- server: input.server,
674
- path: input.path,
675
- mode: input.mode,
676
- changedPath: input.path,
677
- approvalStatus: "host-managed-required",
678
- approvalRequired: true,
679
- })
680
-
681
- try {
682
- await (audit.captureSnapshots?.({ server: input.server, path: input.path, before, after }) ?? Promise.resolve())
683
- const payload: ToolPayload<{ name: string; longname: string }[] | string[]> = {
684
- tool: "remote_write_file",
685
- server: input.server,
686
- execution: { attempted: true, completed: true },
687
- audit: { logWritten: false, snapshotStatus: "written" },
688
- }
689
- if (!logWritten) {
690
- return withAuditFlag(
691
- "partial_failure",
692
- {
693
- ...payload,
694
- message: "remote write succeeded but audit log write failed",
695
- audit: { logWritten: false, snapshotStatus: "written" },
696
- },
697
- false,
698
- )
699
- }
700
- return withAuditFlag("ok", payload, true)
701
- } catch (error) {
702
- return withAuditFlag(
703
- "partial_failure",
704
- {
705
- tool: "remote_write_file",
706
- server: input.server,
707
- message: `remote write succeeded but audit finalization failed: ${(error as Error).message}`,
708
- execution: { attempted: true, completed: true },
709
- audit: { logWritten: false, snapshotStatus: "partial-failure" },
710
- },
711
- logWritten,
712
- )
713
- }
714
- }
715
-
716
- const remotePatchFile = async (input: RemotePatchFileInput): Promise<ToolResult> => {
717
- const logReady = await preflightLog("remote_patch_file", input.server)
718
- if (logReady) {
719
- return logReady
720
- }
721
-
722
- const resolved = await resolveServer(
723
- "remote_patch_file",
724
- input.server,
725
- { path: input.path },
726
- "host-managed-required",
727
- )
728
- if (resolved.result) {
729
- return resolved.result
730
- }
731
-
732
- const connection = toConnectConfig("remote_patch_file", resolved.server!)
733
- if ("status" in connection) {
734
- return logAuthFailure("remote_patch_file", "host-managed-required", { path: input.path }, connection)
735
- }
736
-
737
- let before: string
738
- try {
739
- before = await ssh.readFile(connection, input.path)
740
- } catch (error) {
741
- const payload: ToolPayload<{ size: number; mode: number; isFile: boolean; isDirectory: boolean }> = {
742
- tool: "remote_patch_file",
743
- server: input.server,
744
- code: "SSH_READ_FAILED",
745
- message: (error as Error).message,
746
- execution: { attempted: true, completed: false },
747
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
748
- }
749
- const logWritten = await appendLogSafe({
750
- tool: "remote_patch_file",
751
- server: input.server,
752
- path: input.path,
753
- approvalStatus: "host-managed-required",
754
- code: "SSH_READ_FAILED",
755
- message: payload.message,
756
- })
757
- return withAuditFlag("error", payload, logWritten)
758
- }
759
-
760
- let after: string
761
- try {
762
- after = applyUnifiedPatch(before, input.patch)
763
- } catch (error) {
764
- const payload: ToolPayload<{ name: string; longname: string }[] | string[]> = {
765
- tool: "remote_patch_file",
766
- server: input.server,
767
- code: "PATCH_APPLY_FAILED",
768
- message: (error as Error).message,
769
- execution: { attempted: false, completed: false },
770
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
771
- }
772
- const logWritten = await appendLogSafe({
773
- tool: "remote_patch_file",
774
- server: input.server,
775
- path: input.path,
776
- approvalStatus: "host-managed-required",
777
- code: "PATCH_APPLY_FAILED",
778
- message: payload.message,
779
- })
780
- return withAuditFlag("error", payload, logWritten)
781
- }
782
-
783
- try {
784
- await (audit.preflightSnapshots?.() ?? Promise.resolve())
785
- } catch (error) {
786
- const payload: ToolPayload<{ size: number; mode: number; isFile: boolean; isDirectory: boolean }> = {
787
- tool: "remote_patch_file",
788
- server: input.server,
789
- code: "AUDIT_SNAPSHOT_PREFLIGHT_FAILED",
790
- message: (error as Error).message,
791
- execution: { attempted: false, completed: false },
792
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
793
- }
794
- const logWritten = await appendLogSafe({
795
- tool: "remote_patch_file",
796
- server: input.server,
797
- path: input.path,
798
- approvalStatus: "host-managed-required",
799
- code: "AUDIT_SNAPSHOT_PREFLIGHT_FAILED",
800
- message: payload.message,
801
- })
802
- return withAuditFlag("error", payload, logWritten)
803
- }
804
-
805
- try {
806
- await ssh.writeFile(connection, input.path, after)
807
- } catch (error) {
808
- const payload: ToolPayload<ExecResult> = {
809
- tool: "remote_patch_file",
810
- server: input.server,
811
- code: "SSH_WRITE_FAILED",
812
- message: (error as Error).message,
813
- execution: { attempted: true, completed: false },
814
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
815
- }
816
- const logWritten = await appendLogSafe({
817
- tool: "remote_patch_file",
818
- server: input.server,
819
- path: input.path,
820
- approvalStatus: "host-managed-required",
821
- approvalRequired: true,
822
- code: "SSH_WRITE_FAILED",
823
- message: payload.message,
824
- })
825
- return withAuditFlag("error", payload, logWritten)
826
- }
827
-
828
- const logWritten = await appendLogSafe({
829
- tool: "remote_patch_file",
830
- server: input.server,
831
- path: input.path,
832
- changedPath: input.path,
833
- approvalStatus: "host-managed-required",
834
- approvalRequired: true,
835
- })
836
-
837
- try {
838
- await (audit.captureSnapshots?.({ server: input.server, path: input.path, before, after }) ?? Promise.resolve())
839
- const payload: ToolPayload<{ name: string; longname: string }[] | string[]> = {
840
- tool: "remote_patch_file",
841
- server: input.server,
842
- execution: { attempted: true, completed: true },
843
- audit: { logWritten: false, snapshotStatus: "written" },
844
- }
845
- if (!logWritten) {
846
- return withAuditFlag(
847
- "partial_failure",
848
- {
849
- ...payload,
850
- message: "remote patch succeeded but audit log write failed",
851
- audit: { logWritten: false, snapshotStatus: "written" },
852
- },
853
- false,
854
- )
855
- }
856
- return withAuditFlag("ok", payload, true)
857
- } catch (error) {
858
- return withAuditFlag(
859
- "partial_failure",
860
- {
861
- tool: "remote_patch_file",
862
- server: input.server,
863
- message: `remote patch succeeded but audit finalization failed: ${(error as Error).message}`,
864
- execution: { attempted: true, completed: true },
865
- audit: { logWritten: false, snapshotStatus: "partial-failure" },
866
- },
867
- logWritten,
868
- )
869
- }
870
- }
871
-
872
- const remoteListDir = async (
873
- input: RemoteListDirInput,
874
- ): Promise<ToolResult<{ name: string; longname: string }[] | string[]>> => {
875
- const logReady = await preflightLog<{ name: string; longname: string }[] | string[]>("remote_list_dir", input.server)
876
- if (logReady) {
877
- return logReady
878
- }
879
-
880
- const resolved = await resolveServer(
881
- "remote_list_dir",
882
- input.server,
883
- { path: input.path, recursive: input.recursive, limit: input.limit },
884
- "not-required",
885
- )
886
- if (resolved.result) {
887
- return resolved.result as ToolResult<{ name: string; longname: string }[] | string[]>
888
- }
889
-
890
- try {
891
- const connection = toConnectConfig("remote_list_dir", resolved.server!)
892
- if ("status" in connection) {
893
- return logAuthFailure(
894
- "remote_list_dir",
895
- "not-required",
896
- { path: input.path, recursive: input.recursive ?? false, limit: input.limit ?? 200 },
897
- connection,
898
- )
899
- }
900
-
901
- const entries = await ssh.listDir(connection, input.path, input.recursive ?? false, input.limit ?? 200)
902
- const payload: ToolPayload<{ name: string; longname: string }[] | string[]> = {
903
- tool: "remote_list_dir",
904
- server: input.server,
905
- data: entries,
906
- execution: { attempted: true, completed: true },
907
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
908
- }
909
- const logWritten = await appendLogSafe({
910
- tool: "remote_list_dir",
911
- server: input.server,
912
- path: input.path,
913
- recursive: input.recursive ?? false,
914
- limit: input.limit ?? 200,
915
- approvalStatus: "not-required",
916
- })
917
- return withAuditFlag("ok", payload, logWritten)
918
- } catch (error) {
919
- const payload: ToolPayload<{ name: string; longname: string }[] | string[]> = {
920
- tool: "remote_list_dir",
921
- server: input.server,
922
- code: "SSH_LIST_FAILED",
923
- message: (error as Error).message,
924
- execution: { attempted: true, completed: false },
925
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
926
- }
927
- const logWritten = await appendLogSafe({
928
- tool: "remote_list_dir",
929
- server: input.server,
930
- path: input.path,
931
- approvalStatus: "not-required",
932
- code: "SSH_LIST_FAILED",
933
- message: payload.message,
934
- })
935
- return withAuditFlag("error", payload, logWritten)
936
- }
937
- }
938
-
939
- const remoteStat = async (
940
- input: RemoteStatInput,
941
- ): Promise<ToolResult<{ size: number; mode: number; isFile: boolean; isDirectory: boolean }>> => {
942
- const logReady = await preflightLog<{ size: number; mode: number; isFile: boolean; isDirectory: boolean }>(
943
- "remote_stat",
944
- input.server,
945
- )
946
- if (logReady) {
947
- return logReady
948
- }
949
-
950
- const resolved = await resolveServer("remote_stat", input.server, { path: input.path }, "not-required")
951
- if (resolved.result) {
952
- return resolved.result as ToolResult<{ size: number; mode: number; isFile: boolean; isDirectory: boolean }>
953
- }
954
-
955
- try {
956
- const connection = toConnectConfig("remote_stat", resolved.server!)
957
- if ("status" in connection) {
958
- return logAuthFailure("remote_stat", "not-required", { path: input.path }, connection)
959
- }
960
-
961
- const stat = await ssh.stat(connection, input.path)
962
- const payload: ToolPayload<typeof stat> = {
963
- tool: "remote_stat",
964
- server: input.server,
965
- data: stat,
966
- execution: { attempted: true, completed: true },
967
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
968
- }
969
- const logWritten = await appendLogSafe({
970
- tool: "remote_stat",
971
- server: input.server,
972
- path: input.path,
973
- approvalStatus: "not-required",
974
- })
975
- return withAuditFlag("ok", payload, logWritten)
976
- } catch (error) {
977
- const payload: ToolPayload<{ size: number; mode: number; isFile: boolean; isDirectory: boolean }> = {
978
- tool: "remote_stat",
979
- server: input.server,
980
- code: "SSH_STAT_FAILED",
981
- message: (error as Error).message,
982
- execution: { attempted: true, completed: false },
983
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
984
- }
985
- const logWritten = await appendLogSafe({
986
- tool: "remote_stat",
987
- server: input.server,
988
- path: input.path,
989
- approvalStatus: "not-required",
990
- code: "SSH_STAT_FAILED",
991
- message: payload.message,
992
- })
993
- return withAuditFlag("error", payload, logWritten)
994
- }
995
- }
996
-
997
- const remoteFind = async (input: RemoteFindInput): Promise<ToolResult<ExecResult>> => {
998
- const logReady = await preflightLog<ExecResult>("remote_find", input.server)
999
- if (logReady) {
1000
- return logReady
1001
- }
1002
-
1003
- const resolved = await resolveServer(
1004
- "remote_find",
1005
- input.server,
1006
- { path: input.path, pattern: input.pattern, glob: input.glob, limit: input.limit },
1007
- "not-required",
1008
- )
1009
- if (resolved.result) {
1010
- return resolved.result as ToolResult<ExecResult>
1011
- }
1012
-
1013
- const limit = clampLimit(input.limit, 200)
1014
- const command = input.glob
1015
- ? `find ${quoteShell(input.path)} -name ${quoteShell(input.glob)} | head -n ${limit}`
1016
- : `grep -R -n ${quoteShell(input.pattern)} ${quoteShell(input.path)} | head -n ${limit}`
1017
-
1018
- try {
1019
- const connection = toConnectConfig("remote_find", resolved.server!)
1020
- if ("status" in connection) {
1021
- return logAuthFailure(
1022
- "remote_find",
1023
- "not-required",
1024
- { path: input.path, pattern: input.pattern, glob: input.glob, limit: input.limit },
1025
- connection,
1026
- )
1027
- }
1028
-
1029
- const executed = await ssh.exec(connection, command)
1030
- const payload: ToolPayload<ExecResult> = {
1031
- tool: "remote_find",
1032
- server: input.server,
1033
- data: executed,
1034
- execution: {
1035
- attempted: true,
1036
- completed: true,
1037
- exitCode: executed.exitCode,
1038
- stdoutBytes: byteLength(executed.stdout),
1039
- stderrBytes: byteLength(executed.stderr),
1040
- },
1041
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
1042
- }
1043
- const logWritten = await appendLogSafe({
1044
- tool: "remote_find",
1045
- server: input.server,
1046
- command,
1047
- approvalStatus: "not-required",
1048
- ...executed,
1049
- })
1050
- return withAuditFlag("ok", payload, logWritten)
1051
- } catch (error) {
1052
- const payload: ToolPayload<ExecResult> = {
1053
- tool: "remote_find",
1054
- server: input.server,
1055
- code: "SSH_FIND_FAILED",
1056
- message: (error as Error).message,
1057
- execution: { attempted: true, completed: false },
1058
- audit: { logWritten: false, snapshotStatus: "not-applicable" },
1059
- }
1060
- const logWritten = await appendLogSafe({
1061
- tool: "remote_find",
1062
- server: input.server,
1063
- command,
1064
- approvalStatus: "not-required",
1065
- code: "SSH_FIND_FAILED",
1066
- message: payload.message,
1067
- })
1068
- return withAuditFlag("error", payload, logWritten)
1069
- }
1070
- }
1071
-
1072
- return {
1073
- listServers,
1074
- remoteExec,
1075
- remoteReadFile,
1076
- remoteWriteFile,
1077
- remotePatchFile,
1078
- remoteListDir,
1079
- remoteStat,
1080
- remoteFind,
1081
- }
1082
- }