@runuai/host 0.1.0
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/LICENSE +21 -0
- package/README.md +91 -0
- package/bin/uai-host.mjs +14 -0
- package/db/migrations/0000_host_tasks.sql +12 -0
- package/db/migrations/0001_host_ui.sql +11 -0
- package/db/migrations/0002_host_github_tokens.sql +8 -0
- package/db/migrations/0003_host_ssh_keys.sql +8 -0
- package/db/migrations/0004_host_owner_name.sql +1 -0
- package/db/migrations/meta/_journal.json +41 -0
- package/db/schema.ts +82 -0
- package/images/standard/Dockerfile +232 -0
- package/images/standard/README.md +122 -0
- package/images/standard/container/code-server-settings.json +36 -0
- package/images/standard/container/uai-init +215 -0
- package/images/standard/tool-versions +2 -0
- package/lib/agent.ts +292 -0
- package/lib/agents/claude.ts +343 -0
- package/lib/agents/codex.ts +522 -0
- package/lib/agents/factory.ts +34 -0
- package/lib/agents/mock.ts +133 -0
- package/lib/agents/proc.ts +172 -0
- package/lib/agents/registry.ts +109 -0
- package/lib/agents/types.ts +133 -0
- package/lib/attachments.ts +46 -0
- package/lib/cloud-state.ts +56 -0
- package/lib/command-db.ts +278 -0
- package/lib/db.ts +68 -0
- package/lib/env.ts +140 -0
- package/lib/git-diff.ts +370 -0
- package/lib/git-identity.ts +65 -0
- package/lib/github-tokens.ts +321 -0
- package/lib/orchestrator.ts +975 -0
- package/lib/preview-ports.ts +85 -0
- package/lib/repo-clone.ts +127 -0
- package/lib/runtime-state.ts +120 -0
- package/lib/secrets.ts +71 -0
- package/lib/ssh.ts +186 -0
- package/lib/standard-image.ts +152 -0
- package/lib/task-diff.ts +113 -0
- package/lib/task-status.ts +46 -0
- package/lib/transcript.ts +30 -0
- package/lib/ulid.ts +7 -0
- package/package.json +85 -0
- package/scripts/agent/_common.sh +248 -0
- package/scripts/agent/task-down.sh +113 -0
- package/scripts/agent/task-status.sh +54 -0
- package/scripts/agent/task-up.sh +457 -0
- package/scripts/install/darwin.ts +167 -0
- package/scripts/install/linux.ts +115 -0
- package/scripts/install/types.ts +35 -0
- package/scripts/install/util.ts +39 -0
- package/scripts/install/win.ts +130 -0
- package/src/cli.ts +445 -0
- package/src/index.ts +375 -0
- package/src/load-env.ts +52 -0
- package/src/main.ts +1156 -0
- package/src/paths.ts +64 -0
- package/src/protocol.ts +413 -0
- package/src/ui/server.ts +343 -0
- package/src/ui/types.ts +78 -0
- package/ui/app.js +264 -0
- package/ui/index.html +55 -0
- package/ui/style.css +359 -0
- package/ui/uai-logo-black.svg +9 -0
package/src/paths.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared filesystem paths for the host service + local UI (ADR-028).
|
|
3
|
+
* Used by main.ts (server boot), cli.ts (status/open/logs), and the per-OS
|
|
4
|
+
* install modules so the log/port/asset locations have a single definition.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFileSync } from "node:fs";
|
|
8
|
+
import { homedir } from "node:os";
|
|
9
|
+
import { dirname, join, resolve } from "node:path";
|
|
10
|
+
import { fileURLToPath } from "node:url";
|
|
11
|
+
|
|
12
|
+
import { env } from "../lib/env";
|
|
13
|
+
|
|
14
|
+
const srcDir = dirname(fileURLToPath(import.meta.url)); // host-agent/src
|
|
15
|
+
|
|
16
|
+
/** Base dir for host state — the repo root in a checkout, ~/.uai when
|
|
17
|
+
* installed via npm. Used as the service working directory. */
|
|
18
|
+
export function uaiHome(): string {
|
|
19
|
+
return env.uaiHome;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/** `.env.local` under UAI_HOME — where `setup`/`pair` persist the credential. */
|
|
23
|
+
export function envLocalPath(): string {
|
|
24
|
+
return join(env.uaiHome, ".env.local");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Static UI asset dir (`host-agent/ui`). */
|
|
28
|
+
export function uiAssetsDir(): string {
|
|
29
|
+
return resolve(srcDir, "..", "ui");
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Where the chosen UI port is persisted (`$UAI_DATA_DIR/host.port`). */
|
|
33
|
+
export function uiPortFilePath(): string {
|
|
34
|
+
return join(env.dataDir, "host.port");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Per-OS service log path — informational in `/api/status`, the file
|
|
38
|
+
* `uai-host logs` tails. Linux logs to journald, so returns a hint there. */
|
|
39
|
+
export function serviceLogPath(): string {
|
|
40
|
+
if (process.platform === "darwin") {
|
|
41
|
+
return resolve(homedir(), "Library", "Logs", "Uai", "host.log");
|
|
42
|
+
}
|
|
43
|
+
if (process.platform === "win32") {
|
|
44
|
+
return join(
|
|
45
|
+
process.env.LOCALAPPDATA ?? homedir(),
|
|
46
|
+
"Uai",
|
|
47
|
+
"service",
|
|
48
|
+
"logs",
|
|
49
|
+
"host.log",
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return "journald (journalctl --user -u uai-host)";
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** `@runuai/host` package version (read at runtime; no JSON-import dance). */
|
|
56
|
+
export function packageVersion(): string {
|
|
57
|
+
try {
|
|
58
|
+
const raw = readFileSync(resolve(srcDir, "..", "package.json"), "utf8");
|
|
59
|
+
const pkg = JSON.parse(raw) as { version?: string };
|
|
60
|
+
return pkg.version ?? "0.0.0";
|
|
61
|
+
} catch {
|
|
62
|
+
return "0.0.0";
|
|
63
|
+
}
|
|
64
|
+
}
|
package/src/protocol.ts
ADDED
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
export enum HostErrorCode {
|
|
2
|
+
TaskNotFound = "task_not_found",
|
|
3
|
+
ProjectNotFound = "project_not_found",
|
|
4
|
+
TemplateNotFound = "template_not_found",
|
|
5
|
+
TaskAlreadyRunning = "task_already_running",
|
|
6
|
+
TaskNotRunning = "task_not_running",
|
|
7
|
+
ContainerInitFailed = "container_init_failed",
|
|
8
|
+
ComposeUpFailed = "compose_up_failed",
|
|
9
|
+
ComposeDownFailed = "compose_down_failed",
|
|
10
|
+
WorktreeFailed = "worktree_failed",
|
|
11
|
+
CloneFailed = "clone_failed",
|
|
12
|
+
FetchFailed = "fetch_failed",
|
|
13
|
+
RenderFailed = "render_failed",
|
|
14
|
+
DbFailed = "db_failed",
|
|
15
|
+
NoSuchAgent = "no_such_agent",
|
|
16
|
+
NoSuchPermission = "no_such_permission",
|
|
17
|
+
HostUnavailable = "host_unavailable",
|
|
18
|
+
Internal = "internal",
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type HostCommandResult<T> =
|
|
22
|
+
| { ok: true; value: T }
|
|
23
|
+
| {
|
|
24
|
+
ok: false;
|
|
25
|
+
code: HostErrorCode;
|
|
26
|
+
message: string;
|
|
27
|
+
retryable?: boolean;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export interface CommandContext {
|
|
31
|
+
commandId: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type PermissionDecision = { kind: "accept" } | { kind: "decline" };
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* An agent materialised onto a task (ADR-021/ADR-022). `kind` is the
|
|
38
|
+
* host-advertised agent kind ("claude" | "codex" | "gemini" | …); there is no
|
|
39
|
+
* `role` field. `model` is passed through to the CLI by the adapter. Each
|
|
40
|
+
* agent carries its own first-turn `initialPrompt` and persona `defaultPrompt`.
|
|
41
|
+
* `effort` (when set) is the CLI reasoning level, passed through like `model`.
|
|
42
|
+
*/
|
|
43
|
+
export interface TaskAgent {
|
|
44
|
+
id: string;
|
|
45
|
+
label: string;
|
|
46
|
+
kind: string;
|
|
47
|
+
model?: string;
|
|
48
|
+
effort?: string;
|
|
49
|
+
defaultPrompt?: string;
|
|
50
|
+
initialPrompt?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Host capability advertisement (ADR-021). Sent as a `host.capabilities`
|
|
55
|
+
* frame after auth-success and re-sent when the host's adapter registry
|
|
56
|
+
* changes. The cloud caches it per hostId to drive the task-creation pickers.
|
|
57
|
+
*/
|
|
58
|
+
export interface HostCapabilities {
|
|
59
|
+
agentKinds: Array<{
|
|
60
|
+
kind: string;
|
|
61
|
+
label: string;
|
|
62
|
+
supportedModels: string[];
|
|
63
|
+
defaultModel?: string;
|
|
64
|
+
supportedEfforts: string[];
|
|
65
|
+
defaultEffort?: string;
|
|
66
|
+
}>;
|
|
67
|
+
runtimes: Array<{
|
|
68
|
+
kind: string;
|
|
69
|
+
availableVersions: string[];
|
|
70
|
+
}>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface TaskUpResult {
|
|
74
|
+
composeProject: string;
|
|
75
|
+
worktreePath: string;
|
|
76
|
+
codeServerPort?: number;
|
|
77
|
+
previewPorts?: Array<{ name: string; hostPort: number }>;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export interface TaskDownResult {
|
|
81
|
+
status?: string;
|
|
82
|
+
alreadyGone?: boolean;
|
|
83
|
+
[key: string]: unknown;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface TaskStatusResult {
|
|
87
|
+
composeRunning: boolean;
|
|
88
|
+
containers: string[];
|
|
89
|
+
worktreePresent: boolean;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface CloneRepoInput {
|
|
93
|
+
url: string;
|
|
94
|
+
projectId: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface CloneRepoResult {
|
|
98
|
+
status: "ready" | "error";
|
|
99
|
+
error?: string;
|
|
100
|
+
absolutePath: string;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface TaskCommandTask {
|
|
104
|
+
id: string;
|
|
105
|
+
ownerUserId: string;
|
|
106
|
+
hostId: string;
|
|
107
|
+
name: string;
|
|
108
|
+
slug: string;
|
|
109
|
+
branch: string;
|
|
110
|
+
status: string;
|
|
111
|
+
globalContext?: string;
|
|
112
|
+
reviewerOrder?: string[];
|
|
113
|
+
agents: TaskAgent[];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* A project snapshot carried in a task command. A project is a single repo
|
|
118
|
+
* plus config (ADR-022). `previewPorts` and `env` are JSON strings (the
|
|
119
|
+
* host parses them). `position` orders the clone layout and prompt concat.
|
|
120
|
+
*/
|
|
121
|
+
export interface TaskCommandProject {
|
|
122
|
+
id: string;
|
|
123
|
+
ownerUserId: string;
|
|
124
|
+
name: string;
|
|
125
|
+
slug: string;
|
|
126
|
+
repoUrl: string;
|
|
127
|
+
defaultPrompt: string;
|
|
128
|
+
previewPorts: string;
|
|
129
|
+
env: string;
|
|
130
|
+
toolVersions?: string;
|
|
131
|
+
extra?: string;
|
|
132
|
+
position: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export interface TaskLaunchInput {
|
|
136
|
+
task: TaskCommandTask;
|
|
137
|
+
projects: TaskCommandProject[];
|
|
138
|
+
ownerEmail?: string;
|
|
139
|
+
ownerName?: string;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface TaskDownInput {
|
|
143
|
+
taskId: string;
|
|
144
|
+
task: TaskCommandTask;
|
|
145
|
+
projects: TaskCommandProject[];
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export interface TaskDiffInput {
|
|
149
|
+
taskId: string;
|
|
150
|
+
projects: Array<{ id: string; slug: string }>;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export interface ChannelEnsureInput {
|
|
154
|
+
taskId: string;
|
|
155
|
+
agents: TaskAgent[];
|
|
156
|
+
globalContext?: string;
|
|
157
|
+
projects: Array<{ slug: string; defaultPrompt: string }>;
|
|
158
|
+
branch: string;
|
|
159
|
+
workspacePath: string;
|
|
160
|
+
status: string;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export type DiffStatus =
|
|
164
|
+
| "added"
|
|
165
|
+
| "deleted"
|
|
166
|
+
| "modified"
|
|
167
|
+
| "renamed"
|
|
168
|
+
| "copied"
|
|
169
|
+
| "binary";
|
|
170
|
+
|
|
171
|
+
export interface DiffLine {
|
|
172
|
+
kind: "ctx" | "add" | "del";
|
|
173
|
+
content: string;
|
|
174
|
+
oldNo?: number;
|
|
175
|
+
newNo?: number;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export interface DiffHunk {
|
|
179
|
+
oldStart: number;
|
|
180
|
+
oldLines: number;
|
|
181
|
+
newStart: number;
|
|
182
|
+
newLines: number;
|
|
183
|
+
header: string;
|
|
184
|
+
lines: DiffLine[];
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export interface DiffFile {
|
|
188
|
+
path: string;
|
|
189
|
+
oldPath?: string;
|
|
190
|
+
status: DiffStatus;
|
|
191
|
+
language?: string;
|
|
192
|
+
binary: boolean;
|
|
193
|
+
hunks: DiffHunk[];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
export interface TaskDiffRepo {
|
|
197
|
+
id: string;
|
|
198
|
+
name: string;
|
|
199
|
+
mountPath: string;
|
|
200
|
+
defaultBranch: string;
|
|
201
|
+
files: DiffFile[];
|
|
202
|
+
error?: string;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
export interface TaskDiffResult {
|
|
206
|
+
repos: TaskDiffRepo[];
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export interface HostCommands {
|
|
210
|
+
taskUp(
|
|
211
|
+
ctx: CommandContext,
|
|
212
|
+
input: TaskLaunchInput,
|
|
213
|
+
): Promise<HostCommandResult<TaskUpResult>>;
|
|
214
|
+
taskDown(
|
|
215
|
+
ctx: CommandContext,
|
|
216
|
+
input: TaskDownInput,
|
|
217
|
+
): Promise<HostCommandResult<TaskDownResult>>;
|
|
218
|
+
taskStatus(
|
|
219
|
+
ctx: CommandContext,
|
|
220
|
+
taskId: string,
|
|
221
|
+
): Promise<HostCommandResult<TaskStatusResult>>;
|
|
222
|
+
channelEnsure(
|
|
223
|
+
ctx: CommandContext,
|
|
224
|
+
input: ChannelEnsureInput,
|
|
225
|
+
): Promise<HostCommandResult<void>>;
|
|
226
|
+
channelTeardown(
|
|
227
|
+
ctx: CommandContext,
|
|
228
|
+
taskId: string,
|
|
229
|
+
): Promise<HostCommandResult<void>>;
|
|
230
|
+
channelDeliver(
|
|
231
|
+
ctx: CommandContext,
|
|
232
|
+
taskId: string,
|
|
233
|
+
agentId: string,
|
|
234
|
+
text: string,
|
|
235
|
+
): Promise<HostCommandResult<void>>;
|
|
236
|
+
channelResolvePermission(
|
|
237
|
+
ctx: CommandContext,
|
|
238
|
+
taskId: string,
|
|
239
|
+
agentId: string,
|
|
240
|
+
requestId: string,
|
|
241
|
+
decision: PermissionDecision,
|
|
242
|
+
): Promise<HostCommandResult<void>>;
|
|
243
|
+
cloneRepo(
|
|
244
|
+
ctx: CommandContext,
|
|
245
|
+
input: CloneRepoInput,
|
|
246
|
+
): Promise<HostCommandResult<CloneRepoResult>>;
|
|
247
|
+
taskDiff(
|
|
248
|
+
ctx: CommandContext,
|
|
249
|
+
input: TaskDiffInput,
|
|
250
|
+
): Promise<HostCommandResult<TaskDiffResult>>;
|
|
251
|
+
// Chat attachments live in the task workspace on the host (ADR-015); the
|
|
252
|
+
// cloud relays bytes (base64) in and out, never persisting them.
|
|
253
|
+
attachmentWrite(
|
|
254
|
+
ctx: CommandContext,
|
|
255
|
+
input: { taskId: string; filename: string; dataBase64: string },
|
|
256
|
+
): Promise<HostCommandResult<void>>;
|
|
257
|
+
attachmentRead(
|
|
258
|
+
ctx: CommandContext,
|
|
259
|
+
input: { taskId: string; filename: string },
|
|
260
|
+
): Promise<HostCommandResult<{ dataBase64: string }>>;
|
|
261
|
+
channelInterrupt(
|
|
262
|
+
ctx: CommandContext,
|
|
263
|
+
taskId: string,
|
|
264
|
+
agentId: string,
|
|
265
|
+
): Promise<HostCommandResult<void>>;
|
|
266
|
+
// Append a chat message to the task's shared transcript (<workspace>/.uai/
|
|
267
|
+
// chat.md) so agents can read cross-agent context on demand.
|
|
268
|
+
appendTranscript(
|
|
269
|
+
ctx: CommandContext,
|
|
270
|
+
taskId: string,
|
|
271
|
+
author: string,
|
|
272
|
+
text: string,
|
|
273
|
+
): Promise<HostCommandResult<void>>;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export interface TunnelReqLine {
|
|
277
|
+
method: string;
|
|
278
|
+
url: string;
|
|
279
|
+
headers: [string, string][];
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export type TunnelTarget = "editor" | "preview";
|
|
283
|
+
|
|
284
|
+
export type CloudToHost =
|
|
285
|
+
| {
|
|
286
|
+
kind: "command";
|
|
287
|
+
commandId: string;
|
|
288
|
+
command: keyof HostCommands;
|
|
289
|
+
args: unknown[];
|
|
290
|
+
}
|
|
291
|
+
| { kind: "pong"; ts: number }
|
|
292
|
+
| {
|
|
293
|
+
kind: "tunnel.open";
|
|
294
|
+
tunnelId: string;
|
|
295
|
+
target: TunnelTarget;
|
|
296
|
+
taskId: string;
|
|
297
|
+
name?: string;
|
|
298
|
+
reqLine: TunnelReqLine;
|
|
299
|
+
upgrade: boolean;
|
|
300
|
+
}
|
|
301
|
+
| { kind: "tunnel.data"; tunnelId: string }
|
|
302
|
+
// Signals the request body is complete so the host can end the upstream
|
|
303
|
+
// request (only then does an upstream that reads the body reply). Without
|
|
304
|
+
// this, a body request deadlocks: the host never ends the upstream, so it
|
|
305
|
+
// never responds, so the cloud never sends an ack. (HTTP tunnels only.)
|
|
306
|
+
| { kind: "tunnel.requestEnd"; tunnelId: string }
|
|
307
|
+
| { kind: "tunnel.close"; tunnelId: string; reason?: string }
|
|
308
|
+
// GitHub App connect lifecycle (ADR-027). The cloud forwards the user's
|
|
309
|
+
// refresh token to the host, which encrypts + persists it; the per-task
|
|
310
|
+
// access-token exchange is a host→cloud HTTP POST, not a frame.
|
|
311
|
+
| {
|
|
312
|
+
kind: "gh.connect.set";
|
|
313
|
+
userId: string;
|
|
314
|
+
installationId: number;
|
|
315
|
+
githubLogin: string;
|
|
316
|
+
targetType: "User" | "Organization";
|
|
317
|
+
refreshToken: string;
|
|
318
|
+
refreshTokenExpiresAt?: number;
|
|
319
|
+
}
|
|
320
|
+
| { kind: "gh.connect.clear"; userId: string }
|
|
321
|
+
// Per-user SSH key lifecycle (ADR-029). Keys are generated + stored on the
|
|
322
|
+
// host; only the public key crosses the bridge (ssh.key.ack). `get` fetches
|
|
323
|
+
// without creating, `ensure` creates-if-absent, `delete` removes.
|
|
324
|
+
| { kind: "ssh.key.get"; userId: string }
|
|
325
|
+
| { kind: "ssh.key.ensure"; userId: string }
|
|
326
|
+
| { kind: "ssh.key.delete"; userId: string };
|
|
327
|
+
|
|
328
|
+
export type HostToCloud =
|
|
329
|
+
| { kind: "auth"; token: string; hostId: string }
|
|
330
|
+
| { kind: "host.capabilities"; capabilities: HostCapabilities }
|
|
331
|
+
| {
|
|
332
|
+
kind: "result";
|
|
333
|
+
commandId: string;
|
|
334
|
+
result: HostCommandResult<unknown>;
|
|
335
|
+
}
|
|
336
|
+
| { kind: "event"; event: HostEvent }
|
|
337
|
+
| { kind: "ping"; ts: number }
|
|
338
|
+
| {
|
|
339
|
+
kind: "tunnel.ack";
|
|
340
|
+
tunnelId: string;
|
|
341
|
+
ok: boolean;
|
|
342
|
+
status?: number;
|
|
343
|
+
statusText?: string;
|
|
344
|
+
headers?: [string, string][];
|
|
345
|
+
message?: string;
|
|
346
|
+
}
|
|
347
|
+
| { kind: "tunnel.data"; tunnelId: string }
|
|
348
|
+
| { kind: "tunnel.close"; tunnelId: string; reason?: string }
|
|
349
|
+
// Ack for gh.connect.set / gh.connect.clear (ADR-027).
|
|
350
|
+
| { kind: "gh.connect.ack"; userId: string; ok: true }
|
|
351
|
+
| { kind: "gh.connect.ack"; userId: string; ok: false; error: string }
|
|
352
|
+
// Ack for ssh.key.get / ssh.key.ensure / ssh.key.delete (ADR-029). publicKey
|
|
353
|
+
// is null when the user has no key (after delete, or a get-miss).
|
|
354
|
+
| { kind: "ssh.key.ack"; userId: string; ok: true; publicKey: string | null }
|
|
355
|
+
| { kind: "ssh.key.ack"; userId: string; ok: false; error: string };
|
|
356
|
+
|
|
357
|
+
export type HostEvent =
|
|
358
|
+
| {
|
|
359
|
+
kind: "task.status_changed";
|
|
360
|
+
taskId: string;
|
|
361
|
+
from: string;
|
|
362
|
+
to: string;
|
|
363
|
+
}
|
|
364
|
+
| {
|
|
365
|
+
kind: "task.log";
|
|
366
|
+
taskId: string;
|
|
367
|
+
stream: "stdout" | "stderr";
|
|
368
|
+
chunk: string;
|
|
369
|
+
}
|
|
370
|
+
| {
|
|
371
|
+
kind: "agent.message_delta";
|
|
372
|
+
taskId: string;
|
|
373
|
+
agentId: string;
|
|
374
|
+
chunk: string;
|
|
375
|
+
}
|
|
376
|
+
| {
|
|
377
|
+
kind: "agent.message_complete";
|
|
378
|
+
taskId: string;
|
|
379
|
+
agentId: string;
|
|
380
|
+
fullText: string;
|
|
381
|
+
mentions: string[];
|
|
382
|
+
}
|
|
383
|
+
| {
|
|
384
|
+
kind: "agent.tool_call";
|
|
385
|
+
taskId: string;
|
|
386
|
+
agentId: string;
|
|
387
|
+
tool: string;
|
|
388
|
+
meta: unknown;
|
|
389
|
+
}
|
|
390
|
+
| {
|
|
391
|
+
kind: "agent.permission_request";
|
|
392
|
+
taskId: string;
|
|
393
|
+
agentId: string;
|
|
394
|
+
requestId: string;
|
|
395
|
+
meta: unknown;
|
|
396
|
+
}
|
|
397
|
+
| {
|
|
398
|
+
kind: "agent.exit";
|
|
399
|
+
taskId: string;
|
|
400
|
+
agentId: string;
|
|
401
|
+
reason: string;
|
|
402
|
+
}
|
|
403
|
+
// A host-originated system note for the task channel (ADR-027) — e.g. gh
|
|
404
|
+
// auth expired. The cloud renders it as an author:"system" message.
|
|
405
|
+
| {
|
|
406
|
+
kind: "system.note";
|
|
407
|
+
taskId: string;
|
|
408
|
+
text: string;
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
export interface HostEventStream {
|
|
412
|
+
subscribe(handler: (e: HostEvent) => void): () => void;
|
|
413
|
+
}
|