@runuai/host 0.9.10 → 0.9.12
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/db/migrations/0013_github_credential_generations.sql +11 -0
- package/db/migrations/meta/_journal.json +7 -0
- package/db/schema.ts +18 -0
- package/images/standard/container/code-server-settings.json +38 -11
- package/images/standard/container/uai-init +33 -2
- package/lib/env.ts +6 -1
- package/lib/github-tokens.ts +633 -69
- package/package.json +1 -1
- package/scripts/agent/task-up.sh +23 -7
- package/src/main.ts +225 -22
- package/src/protocol.ts +113 -8
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
ALTER TABLE `host_github_tokens` ADD `generation` integer DEFAULT 0 NOT NULL;
|
|
2
|
+
--> statement-breakpoint
|
|
3
|
+
CREATE TABLE `host_github_credential_generations` (
|
|
4
|
+
`user_id` text PRIMARY KEY NOT NULL,
|
|
5
|
+
`generation` integer DEFAULT 0 NOT NULL,
|
|
6
|
+
`mutation` text DEFAULT 'set' NOT NULL,
|
|
7
|
+
`updated_at` integer NOT NULL
|
|
8
|
+
);
|
|
9
|
+
--> statement-breakpoint
|
|
10
|
+
INSERT INTO `host_github_credential_generations` (`user_id`, `generation`, `mutation`, `updated_at`)
|
|
11
|
+
SELECT `user_id`, 0, 'set', `updated_at` FROM `host_github_tokens`;
|
|
@@ -92,6 +92,13 @@
|
|
|
92
92
|
"when": 1779900013000,
|
|
93
93
|
"tag": "0012_agent_session_attach_key",
|
|
94
94
|
"breakpoints": true
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
"idx": 13,
|
|
98
|
+
"version": "6",
|
|
99
|
+
"when": 1786045000000,
|
|
100
|
+
"tag": "0013_github_credential_generations",
|
|
101
|
+
"breakpoints": true
|
|
95
102
|
}
|
|
96
103
|
]
|
|
97
104
|
}
|
package/db/schema.ts
CHANGED
|
@@ -62,6 +62,11 @@ export type NewHostEventRow = typeof hostEvents.$inferInsert;
|
|
|
62
62
|
export const githubTokens = sqliteTable("host_github_tokens", {
|
|
63
63
|
userId: text("user_id").primaryKey(),
|
|
64
64
|
installationId: integer("installation_id").notNull(),
|
|
65
|
+
/** Generation of the cloud binding whose credential this row contains.
|
|
66
|
+
* Readers trust the token only while it matches the durable mutation fence
|
|
67
|
+
* below, so a crash between claiming a newer generation and deleting this
|
|
68
|
+
* row leaves a safely inert old ciphertext rather than a usable old token. */
|
|
69
|
+
generation: integer("generation").notNull().default(0),
|
|
65
70
|
// ADR-033: holds a long-lived ACCESS token when kind="access", or a refresh
|
|
66
71
|
// token when kind="refresh" (legacy ADR-027). Encrypted at rest either way.
|
|
67
72
|
refreshTokenCt: blob("refresh_token_ct").notNull(),
|
|
@@ -75,6 +80,19 @@ export const githubTokens = sqliteTable("host_github_tokens", {
|
|
|
75
80
|
export type GithubToken = typeof githubTokens.$inferSelect;
|
|
76
81
|
export type NewGithubToken = typeof githubTokens.$inferInsert;
|
|
77
82
|
|
|
83
|
+
// Durable monotonic tombstone for GitHub credential mutations. Kept separate
|
|
84
|
+
// from the token row so a disconnect can reject a delayed older connect even
|
|
85
|
+
// after the encrypted credential itself has been deleted.
|
|
86
|
+
export const githubCredentialGenerations = sqliteTable(
|
|
87
|
+
"host_github_credential_generations",
|
|
88
|
+
{
|
|
89
|
+
userId: text("user_id").primaryKey(),
|
|
90
|
+
generation: integer("generation").notNull().default(0),
|
|
91
|
+
mutation: text("mutation").notNull().default("set"),
|
|
92
|
+
updatedAt: integer("updated_at").notNull(),
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
|
|
78
96
|
// Per-user ed25519 SSH key (ADR-029). Generated ON the host; the private key
|
|
79
97
|
// lives encrypted at rest with the host master key (same secret-blind pattern
|
|
80
98
|
// as host_github_tokens — ADR-015). The cloud only ever sees `public_key`,
|
|
@@ -9,28 +9,55 @@
|
|
|
9
9
|
"workbench.welcomePage.walkthroughs.openOnInstall": false,
|
|
10
10
|
"window.commandCenter": false,
|
|
11
11
|
"chat.commandCenter.enabled": false,
|
|
12
|
+
"chat.viewSessions.enabled": false,
|
|
13
|
+
"chat.disableAIFeatures": true,
|
|
12
14
|
"update.mode": "none",
|
|
13
15
|
"extensions.autoCheckUpdates": false,
|
|
14
16
|
"extensions.autoUpdate": false,
|
|
15
17
|
"git.openRepositoryInParentFolders": "always",
|
|
16
|
-
"workbench.activityBar.location": "top",
|
|
17
|
-
"chat.viewSessions.enabled": false,
|
|
18
18
|
"workbench.secondarySideBar.defaultVisibility": "hidden",
|
|
19
|
+
|
|
20
|
+
"//4": "Colour theme. Overwritten at FIRST init by uai-init when the task",
|
|
21
|
+
"//5": "carries UAI_EDITOR_THEME (ADR-091); this value is the fallback for",
|
|
22
|
+
"//6": "tasks created before that shipped, or created outside a browser.",
|
|
19
23
|
"workbench.colorTheme": "Dark 2026",
|
|
20
24
|
|
|
21
|
-
"//
|
|
22
|
-
"//
|
|
23
|
-
"//
|
|
24
|
-
"//
|
|
25
|
+
"//7": "An editor, not an IDE (ADR-092). This pane is one of three in a",
|
|
26
|
+
"//8": "task's right panel and is often half a screen wide, so every bar it",
|
|
27
|
+
"//9": "does not need is width the code does. Cut here is chrome only: the",
|
|
28
|
+
"//10": "status bar, the layout buttons, the menu bar, the empty-editor",
|
|
29
|
+
"//11": "watermark, Open Editors, the minimap and the terminal's chat hint.",
|
|
30
|
+
"//12": "Breadcrumbs STAY: with the status bar gone they are the only thing",
|
|
31
|
+
"//13": "naming the path, and a tab reading 'route.ts' is worth little in a",
|
|
32
|
+
"//14": "repo with eleven of them.",
|
|
33
|
+
"workbench.statusBar.visible": false,
|
|
34
|
+
"workbench.layoutControl.enabled": false,
|
|
35
|
+
"window.menuBarVisibility": "hidden",
|
|
36
|
+
"workbench.editor.empty.hint": "hidden",
|
|
37
|
+
"explorer.openEditors.visible": 0,
|
|
38
|
+
"editor.minimap.enabled": false,
|
|
39
|
+
"terminal.integrated.initialHint": false,
|
|
40
|
+
|
|
41
|
+
"//15": "Hide the activity bar and its custom title strip. Explorer stays",
|
|
42
|
+
"//16": "open; the Editor pane's own toolbar provides the pointer route to",
|
|
43
|
+
"//17": "Terminal. Individual pins still live in the BROWSER's IndexedDB; the",
|
|
44
|
+
"//18": "guarded migration keeps the requested containers ready if a user",
|
|
45
|
+
"//19": "later restores the bar.",
|
|
46
|
+
"workbench.activityBar.location": "hidden",
|
|
47
|
+
|
|
48
|
+
"//22": "Workspace Trust — every task runs in an isolated container off the",
|
|
49
|
+
"//23": "user's own repos. The 'do you trust this folder?' prompt is pure",
|
|
50
|
+
"//24": "friction here; the user already trusts their code and the container",
|
|
51
|
+
"//25": "is the security boundary.",
|
|
25
52
|
"security.workspace.trust.enabled": false,
|
|
26
53
|
|
|
27
|
-
"//
|
|
28
|
-
"//
|
|
29
|
-
"//
|
|
30
|
-
"//
|
|
54
|
+
"//26": "Port forwarding — code-server's own auto-forward / 'Open in Browser'",
|
|
55
|
+
"//27": "popup can't reach anything useful here: dev servers are exposed",
|
|
56
|
+
"//28": "through uai's preview tunnel (ADR-025), not code-server's forwarder.",
|
|
57
|
+
"//29": "Disable it so the popup doesn't mislead.",
|
|
31
58
|
"remote.autoForwardPorts": false,
|
|
32
59
|
"remote.restoreForwardedPorts": false,
|
|
33
60
|
|
|
34
|
-
"//
|
|
61
|
+
"//30": "Integrated terminal uses zsh + oh-my-zsh (the node login shell).",
|
|
35
62
|
"terminal.integrated.defaultProfile.linux": "zsh"
|
|
36
63
|
}
|
|
@@ -219,11 +219,41 @@ cs_source_seed="$(dirname "${BASH_SOURCE[0]}")/code-server-settings.json"
|
|
|
219
219
|
if [ ! -f "$cs_seed" ] && [ -f "$cs_source_seed" ]; then
|
|
220
220
|
cs_seed="$cs_source_seed"
|
|
221
221
|
fi
|
|
222
|
+
|
|
223
|
+
# ADR-091: seed the Editor pane in the palette the task was created from.
|
|
224
|
+
# UAI_EDITOR_THEME carries a VS Code theme LABEL ("Quiet Light"), already
|
|
225
|
+
# resolved cloud-side from the theme registry — the container never maps a Uai
|
|
226
|
+
# palette id and never sees a raw user string. Unset is the normal state for a
|
|
227
|
+
# task created before this shipped or outside a browser; the seed's own baked
|
|
228
|
+
# default then stands.
|
|
229
|
+
#
|
|
230
|
+
# Applied ONLY while seeding. An existing settings.json is still left entirely
|
|
231
|
+
# alone, so a user's own editor tweaks survive a restart — which is also why
|
|
232
|
+
# changing the app's palette later does not re-theme an already-created task
|
|
233
|
+
# (docs/theming.md). If jq is missing or the filter fails, fall through to the
|
|
234
|
+
# plain copy: a themed editor is worth less than an editor that starts.
|
|
235
|
+
seed_code_server_settings() {
|
|
236
|
+
local seed="$1" dest="$2" theme="${UAI_EDITOR_THEME:-}"
|
|
237
|
+
if [ -n "$theme" ] && command -v jq >/dev/null 2>&1; then
|
|
238
|
+
if jq --arg theme "$theme" '.["workbench.colorTheme"] = $theme' \
|
|
239
|
+
"$seed" >"$dest.tmp" 2>/dev/null && [ -s "$dest.tmp" ]; then
|
|
240
|
+
mv "$dest.tmp" "$dest"
|
|
241
|
+
log "seeded code-server settings (theme: $theme)"
|
|
242
|
+
return 0
|
|
243
|
+
fi
|
|
244
|
+
rm -f "$dest.tmp"
|
|
245
|
+
log "warning: could not apply UAI_EDITOR_THEME=$theme; using image default"
|
|
246
|
+
fi
|
|
247
|
+
cp "$seed" "$dest" || return 1
|
|
248
|
+
log "seeded code-server settings"
|
|
249
|
+
}
|
|
250
|
+
|
|
222
251
|
if [ ! -f "$cs_user_dir/settings.json" ]; then
|
|
223
252
|
if [ ! -f "$cs_seed" ]; then
|
|
224
253
|
log "warning: code-server settings seed missing at $cs_seed"
|
|
225
|
-
elif mkdir -p "$cs_user_dir" &&
|
|
226
|
-
|
|
254
|
+
elif mkdir -p "$cs_user_dir" &&
|
|
255
|
+
seed_code_server_settings "$cs_seed" "$cs_user_dir/settings.json"; then
|
|
256
|
+
:
|
|
227
257
|
else
|
|
228
258
|
log "warning: could not seed code-server settings at $cs_user_dir/settings.json"
|
|
229
259
|
fi
|
|
@@ -238,6 +268,7 @@ else
|
|
|
238
268
|
--disable-workspace-trust \
|
|
239
269
|
--auth none \
|
|
240
270
|
--disable-telemetry \
|
|
271
|
+
--disable-update-check \
|
|
241
272
|
--bind-addr 0.0.0.0:8080 \
|
|
242
273
|
"$editor_root" \
|
|
243
274
|
>/tmp/code-server.log 2>&1 &
|
package/lib/env.ts
CHANGED
|
@@ -22,9 +22,14 @@ const schema = z.object({
|
|
|
22
22
|
UAI_HOST_ID: z.string().min(1).optional(),
|
|
23
23
|
// Host master key file (ADR-027) — 32 bytes, AES-256-GCM for the host
|
|
24
24
|
// secret store. Defaults to $UAI_DATA_DIR/host-master.key; generated on
|
|
25
|
-
// first use.
|
|
25
|
+
// first use.
|
|
26
26
|
UAI_HOST_KEY_FILE: z.string().min(1).optional(),
|
|
27
|
+
// Transitional single-user GitHub PAT, for the one user who has not
|
|
28
|
+
// connected GitHub. Inert without UAI_GH_PAT_FALLBACK_USER_ID: the PAT is a
|
|
29
|
+
// host-wide credential, so it may only be injected into tasks owned by the
|
|
30
|
+
// user it belongs to. See setupTaskGithub().
|
|
27
31
|
UAI_GH_PAT_FALLBACK: z.string().min(1).optional(),
|
|
32
|
+
UAI_GH_PAT_FALLBACK_USER_ID: z.string().min(1).optional(),
|
|
28
33
|
UAI_WORKSPACE_ROOT: z.string().min(1).default("~/.uai"),
|
|
29
34
|
// Crash reporting (ADR-071, docs/observability.md). Default-ON for
|
|
30
35
|
// installed hosts via the DSN baked into lib/obs.ts; UAI_SENTRY_DSN
|