File size: 2,975 Bytes
488d96b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
/**
 * The frames a shell process and its host exchange.
 *
 * A command runs in its own Web Worker, which owns no filesystem: the VFS
 * stays in the host worker and every read or write is a request on this
 * channel. Blocking the child on a reply is impossible here (that would need
 * `SharedArrayBuffer`, which requires a cross-origin isolation this deployment
 * cannot have), so the filesystem face is asynchronous end to end.
 * @module @deepseek-ai/dsh-experimental-webworker-runtime/src/shell/process/protocol
 */

/** The first frame a process worker receives; it also selects its role. */
export interface ShellStartFrame {
  t: 'shell-start'
  /** Command source for `bash -c`, or undefined when `argv` names a program directly. */
  script?: string | undefined
  /** The program and arguments, used when `script` is absent. */
  argv: readonly string[]
  /** Working directory the command starts in. */
  cwd: string
  /** Environment the command starts with. */
  env: Record<string, string>
  /** Everything on standard input. */
  stdin: string
}

/** Output produced by the command, forwarded as it is written. */
export interface ShellOutputFrame {
  t: 'shell-out'
  stream: 'stdout' | 'stderr'
  text: string
}

/** The command settled; the process worker closes itself right after. */
export interface ShellExitFrame {
  t: 'shell-exit'
  code: number
}

/** A signal the command is asked to honor (the terminate ladder's first rung). */
export interface ShellSignalFrame {
  t: 'shell-signal'
}

/** Filesystem operations a process worker can ask its host to perform. */
export type FilesystemOperation = 'stat' | 'list' | 'readText' | 'writeText' | 'mkdir' | 'remove' | 'rename'

/** One filesystem call, awaiting its reply by `id`. */
export interface FilesystemCallFrame {
  t: 'fs-call'
  id: number
  op: FilesystemOperation
  args: readonly unknown[]
}

/**
 * One filesystem reply. A failure carries the Node error `code` because the
 * utilities branch on it (`ENOENT` prints "No such file or directory"), and an
 * Error instance does not survive structured cloning with its class.
 */
export interface FilesystemReplyFrame {
  t: 'fs-reply'
  id: number
  value?: unknown
  failure?: { code?: string | undefined; message: string }
}

/** Everything the host sends to a process worker. */
export type ToProcessFrame = ShellStartFrame | ShellSignalFrame | FilesystemReplyFrame

/** Everything a process worker sends to its host. */
export type FromProcessFrame = ShellOutputFrame | ShellExitFrame | FilesystemCallFrame

/**
 * Whether a message is the frame that turns a fresh worker into a shell
 * process. The host worker's entry reads this to pick its role.
 * @param data - the raw message payload.
 * @returns true when the payload starts a shell process.
 */
export function isShellStartFrame(data: unknown): data is ShellStartFrame {
  return typeof data === 'object' && data !== null && (data as { t?: unknown }).t === 'shell-start'
}