Skip to content

Commit d963fd4

Browse files
committed
Release v0.2.54
1 parent 62820ca commit d963fd4

217 files changed

Lines changed: 17609 additions & 1182 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎RELEASE.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
{
2-
"version": "0.2.53",
2+
"version": "0.2.54",
33
"license": "FSL-1.1-ALv2",
44
"baselineSha256": "7847f65a9b20b366a60d52d19ca3cd6bdd1bb09b8cb506659a270f27a21d8877",
55
"e2b": {
66
"recipeSha256": "761809ee2efa2b6c750451ace29283cbb8e55b6ce3b5b14c2bb4509df71df801",
77
"profileSha256": "414dbecee2d47de36016ea05c736e2edff3a0deb298babf923887ea184a01c10"
88
},
99
"dxd": {
10-
"url": "https://github.com/dxcode-dev/dxcode/releases/download/v0.2.53/dxd-linux-x64",
10+
"url": "https://github.com/dxcode-dev/dxcode/releases/download/v0.2.54/dxd-linux-x64",
1111
"sha256": "6d2409188da917eaf4fbe03e7be6c25293442b5fa2901ba07c844e16b2adff5b"
1212
},
1313
"toolchain": {

‎THIRD_PARTY_NOTICES.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,18 @@ without a second, embedded copy.
2424
- License: MIT
2525
- License text: <https://github.com/coder/ghostty-web/blob/main/LICENSE>
2626

27+
## Waymote video startup
28+
29+
`third-party/waymote/@rockorager__waymote@0.1.6.patch` makes Waymote's
30+
browser SDK decode everything its gateway sends a new viewer, the latest full
31+
picture and up to 240 frames since. The SDK holds 241 frames instead of 6
32+
while its decoder is set up, and drops frames for a long decoder queue only
33+
once a picture is showing.
34+
35+
- Source: <https://github.com/rockorager/waymote>
36+
- License: MIT
37+
- License text: <https://github.com/rockorager/waymote/blob/main/LICENSE>
38+
2739
## ElevenLabs UI waveform
2840

2941
`apps/web/src/shared/ui/waveform.tsx` adapts the base canvas waveform from

‎alchemy.run.ts‎

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { decodeGitHubAppDeploymentConfiguration } from "./packages/domain/src/so
3939
import {
4040
attachmentBucketCors,
4141
attachmentSigningTokenProps,
42+
workerLogsTokenProps,
4243
} from "./scripts/attachment-deployment.mjs";
4344

4445
type RecordValue<T> = T extends Record<string, infer Value> ? Value : never;
@@ -363,6 +364,17 @@ export default Alchemy.Stack(
363364
githubCopilotClientId: Config.option(
364365
Config.NonEmptyString("DX_GITHUB_COPILOT_CLIENT_ID"),
365366
),
367+
// Where Core files in-app bug reports, with which labels, and who
368+
// may open any report's files; see wiki/product-architecture.md.
369+
bugReportRepository: Config.option(
370+
Config.NonEmptyString("DX_BUG_REPORT_REPOSITORY"),
371+
),
372+
bugReportLabels: Config.option(
373+
Config.NonEmptyString("DX_BUG_REPORT_LABELS"),
374+
),
375+
bugReportReaders: Config.option(
376+
Config.NonEmptyString("DX_BUG_REPORT_READERS"),
377+
),
366378
modelDeploymentProviders: Config.option(
367379
Config.NonEmptyString("DX_MODEL_DEPLOYMENT_PROVIDERS"),
368380
),
@@ -650,13 +662,26 @@ export default Alchemy.Stack(
650662
credentials: (yield* HeldSecret("AttachmentR2Credentials", {
651663
value: Option.getOrUndefined(configuration.attachmentR2Credentials),
652664
})).value,
665+
workerLogs: undefined,
653666
};
654667
const token = yield* Cloudflare.ApiToken.AccountApiToken(
655668
"AttachmentSigningToken",
656669
attachmentSigningTokenProps(accountId, configuration.bucketName),
657670
);
671+
// Read-only Workers Observability on this account, for the Worker
672+
// logs a bug report carries. Self-hosts that cannot mint tokens go
673+
// without, and their reports say Worker logs are unavailable.
674+
const logsToken = yield* Cloudflare.ApiToken.AccountApiToken(
675+
"WorkerLogsToken",
676+
workerLogsTokenProps(accountId, configuration.workerName),
677+
);
658678
return {
659679
endpoint,
680+
workerLogs: Output.map(logsToken.value, (value) =>
681+
Redacted.make(
682+
JSON.stringify({ accountId, token: Redacted.value(value) }),
683+
),
684+
),
660685
credentials: Output.map(
661686
Output.all(token.tokenId, token.value),
662687
([tokenId, value]) =>
@@ -863,6 +888,17 @@ export default Alchemy.Stack(
863888
configuration.githubCopilotClientId.value,
864889
}
865890
: {}),
891+
...(Option.isSome(configuration.bugReportRepository)
892+
? {
893+
DX_BUG_REPORT_REPOSITORY: configuration.bugReportRepository.value,
894+
}
895+
: {}),
896+
...(Option.isSome(configuration.bugReportLabels)
897+
? { DX_BUG_REPORT_LABELS: configuration.bugReportLabels.value }
898+
: {}),
899+
...(Option.isSome(configuration.bugReportReaders)
900+
? { DX_BUG_REPORT_READERS: configuration.bugReportReaders.value }
901+
: {}),
866902
...(Option.isSome(configuration.modelDeploymentProviders)
867903
? {
868904
DX_MODEL_DEPLOYMENT_PROVIDERS:
@@ -901,6 +937,9 @@ export default Alchemy.Stack(
901937
: { DX_TURNSTILE_SECRET_KEY: turnstile.secret }),
902938
DX_CONFIG_ENCRYPTION_KEYS: encryptionKeyring,
903939
DX_ATTACHMENT_R2_CREDENTIALS: attachmentSigning.credentials,
940+
...(attachmentSigning.workerLogs === undefined
941+
? {}
942+
: { DX_WORKER_LOGS: attachmentSigning.workerLogs }),
904943
DX_INTEGRATION_GITHUB_APP: githubApp,
905944
DX_WORKLOAD_IDENTITY_SIGNING_KEYS: workloadIdentitySigningKeys,
906945
...(e2bApiKey === undefined ? {} : { E2B_API_KEY: e2bApiKey }),

‎apps/core/migration-manifest.json‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@
143143
"filename": "0026_release_v0.2.52.sql",
144144
"sha256": "59fc1798166c844a29add8178e6ae5e3543274d72cc127241a0d259773d56d26",
145145
"classification": "non-destructive"
146+
},
147+
{
148+
"filename": "0027_release_v0.2.54.sql",
149+
"sha256": "5956ea9287461a35b334e56a74bb8e2b829d40eb2029bd744fc5fca3f9a45515",
150+
"classification": "non-destructive"
146151
}
147152
],
148153
"durableObjects": [
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
-- A bug report a person sent from a Thread. The message and the redacted
2+
-- diagnostics they chose live in DX_STORAGE under `bug-reports/<id>/`; this
3+
-- row lists the report under Your Reports and records where Core filed it:
4+
-- a new issue (`filed`), a comment on an open issue it duplicates
5+
-- (`commented`), nowhere because the deployment names no repository
6+
-- (`saved`), or not at all (`failed`).
7+
CREATE TABLE bug_report (
8+
id TEXT PRIMARY KEY CHECK (id GLOB 'bug_*' AND length(id) = 36),
9+
reporter_user_id TEXT NOT NULL,
10+
thread_id TEXT NOT NULL,
11+
message TEXT NOT NULL CHECK (length(message) BETWEEN 1 AND 10000),
12+
status TEXT NOT NULL
13+
CHECK (status IN ('filing', 'filed', 'commented', 'saved', 'failed')),
14+
issue_number INTEGER CHECK (issue_number IS NULL OR issue_number > 0),
15+
issue_url TEXT CHECK (issue_url IS NULL OR length(issue_url) <= 2048),
16+
image_count INTEGER NOT NULL CHECK (image_count BETWEEN 0 AND 5),
17+
diagnostics TEXT NOT NULL,
18+
created_at TEXT NOT NULL,
19+
updated_at TEXT NOT NULL
20+
) WITHOUT ROWID;
21+
22+
CREATE INDEX bug_report_reporter_created
23+
ON bug_report (reporter_user_id, created_at DESC);
24+
25+
-- Thread families (issue #1100). `parent_thread_id` stays the family's root,
26+
-- which sharing, archive, and delete act on. `started_by_thread_id` records
27+
-- the family member whose agent started the Thread, so clients can show the
28+
-- family as a tree. NULL for a root, and for a Thread started before this
29+
-- column existed or whose starter was deleted: those list under the root.
30+
ALTER TABLE threads ADD COLUMN started_by_thread_id TEXT
31+
REFERENCES threads (id) ON DELETE SET NULL;
32+
33+
CREATE INDEX threads_started_by_idx ON threads (started_by_thread_id)
34+
WHERE started_by_thread_id IS NOT NULL;
35+
36+
-- Scripts saved in a Project's Orb settings, so a repository works in Orbs
37+
-- without committing setup files. Pre-clone runs before dx clones the
38+
-- repository and pre-setup runs before `.agents/setup`; desktop-resume is
39+
-- reserved for the Desktop. An empty script is deleted, not stored.
40+
CREATE TABLE project_orb_script (
41+
project_id TEXT NOT NULL REFERENCES projects(id) ON DELETE CASCADE,
42+
kind TEXT NOT NULL CHECK (kind IN ('pre-clone', 'pre-setup', 'desktop-resume')),
43+
body TEXT NOT NULL CHECK (length(CAST(body AS BLOB)) BETWEEN 1 AND 32768),
44+
updated_by_user_id TEXT NOT NULL CHECK (length(updated_by_user_id) BETWEEN 1 AND 128),
45+
updated_at TEXT NOT NULL CHECK (length(updated_at) BETWEEN 20 AND 40),
46+
PRIMARY KEY (project_id, kind)
47+
);
48+
49+
-- Every change to a script, kept after the Project is gone. Scripts run in
50+
-- every member's Orbs, so the audit names who changed which script and the
51+
-- digest of what it became.
52+
CREATE TABLE project_orb_script_audit (
53+
id TEXT PRIMARY KEY NOT NULL,
54+
project_id TEXT NOT NULL CHECK (length(project_id) BETWEEN 1 AND 128),
55+
kind TEXT NOT NULL CHECK (kind IN ('pre-clone', 'pre-setup', 'desktop-resume')),
56+
action TEXT NOT NULL CHECK (action IN ('set', 'clear')),
57+
body_sha256 TEXT CHECK (
58+
(action = 'set' AND length(body_sha256) = 64)
59+
OR (action = 'clear' AND body_sha256 IS NULL)
60+
),
61+
actor_user_id TEXT NOT NULL CHECK (length(actor_user_id) BETWEEN 1 AND 128),
62+
request_id TEXT NOT NULL CHECK (length(request_id) BETWEEN 1 AND 128),
63+
occurred_at TEXT NOT NULL
64+
);
65+
66+
CREATE INDEX project_orb_script_audit_project_time_idx
67+
ON project_orb_script_audit(project_id, occurred_at DESC, id DESC);
68+
69+
CREATE TRIGGER project_orb_script_audit_immutable_update
70+
BEFORE UPDATE ON project_orb_script_audit
71+
BEGIN
72+
SELECT RAISE(ABORT, 'project orb script audit is immutable');
73+
END;
74+
75+
CREATE TRIGGER project_orb_script_audit_immutable_delete
76+
BEFORE DELETE ON project_orb_script_audit
77+
BEGIN
78+
SELECT RAISE(ABORT, 'project orb script audit is immutable');
79+
END;
80+
81+
-- A plugin row is either a code plugin or a bundle in Claude Code's format.
82+
-- A bundle's skills and MCP servers live in their own stores and point back
83+
-- at the plugin that installed them; a disabled or removed bundle hides them
84+
-- from new Threads.
85+
ALTER TABLE trusted_plugin ADD COLUMN kind TEXT NOT NULL DEFAULT 'code'
86+
CHECK (kind IN ('code', 'bundle'));
87+
88+
ALTER TABLE skill ADD COLUMN plugin_id TEXT
89+
REFERENCES trusted_plugin (id) ON DELETE RESTRICT;
90+
91+
ALTER TABLE mcp_server ADD COLUMN plugin_id TEXT
92+
REFERENCES trusted_plugin (id) ON DELETE RESTRICT;
93+
94+
CREATE INDEX skill_plugin_idx ON skill (plugin_id) WHERE plugin_id IS NOT NULL;
95+
96+
CREATE INDEX mcp_server_plugin_idx ON mcp_server (plugin_id)
97+
WHERE plugin_id IS NOT NULL;

‎apps/core/package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@dx/core",
33
"private": true,
4-
"version": "0.2.53",
4+
"version": "0.2.54",
55
"license": "FSL-1.1-ALv2",
66
"type": "module",
77
"scripts": {

‎apps/core/production-bindings.json‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,24 @@
4949
"type": "text",
5050
"provenance": "alchemy"
5151
},
52+
{
53+
"name": "DX_BUG_REPORT_LABELS",
54+
"type": "text",
55+
"provenance": "alchemy",
56+
"optional": true
57+
},
58+
{
59+
"name": "DX_BUG_REPORT_READERS",
60+
"type": "text",
61+
"provenance": "alchemy",
62+
"optional": true
63+
},
64+
{
65+
"name": "DX_BUG_REPORT_REPOSITORY",
66+
"type": "text",
67+
"provenance": "alchemy",
68+
"optional": true
69+
},
5270
{
5371
"name": "DX_CONFIG_ENCRYPTION_KEYS",
5472
"type": "secret",
@@ -256,6 +274,12 @@
256274
"provenance": "alchemy",
257275
"optional": true
258276
},
277+
{
278+
"name": "DX_WORKER_LOGS",
279+
"type": "secret",
280+
"provenance": "alchemy",
281+
"optional": true
282+
},
259283
{
260284
"name": "DX_WORKLOAD_IDENTITY_AUDIENCE_POLICIES",
261285
"type": "text",

‎apps/core/src/agents/built-in-skills.ts‎

Lines changed: 55 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ import writingPrompts from "./built-in-skills/writing-prompts/skill.md?raw";
4040
* packages every import of a file named `SKILL.md` and rejects one with
4141
* `?raw`. The workspace copy is named `SKILL.md`.
4242
*/
43-
export const BUILT_IN_SKILLS_DIRECTORY = "/home/user/.local/share/dx/skills";
43+
const DX_SHARE = "/home/user/.local/share/dx";
44+
export const BUILT_IN_SKILLS_DIRECTORY = `${DX_SHARE}/skills`;
4445
const DIGEST_FILE = ".dx-digest";
4546

4647
/** Every file of every built-in skill, keyed by its path in the skill. */
@@ -127,15 +128,21 @@ export const builtInSkillDefinitions: ReadonlyArray<SkillDefinition> =
127128

128129
const encoder = new TextEncoder();
129130

130-
const sha256 = async (value: string) =>
131+
export const sha256 = async (value: string) =>
131132
Array.from(
132133
new Uint8Array(
133134
await crypto.subtle.digest("SHA-256", encoder.encode(value)),
134135
),
135136
(byte) => byte.toString(16).padStart(2, "0"),
136137
).join("");
137138

138-
const files = sources.flatMap(({ name, files }) =>
139+
/** One file of an installed skill, at `<skill name>/<path in the skill>`. */
140+
export interface SkillFile {
141+
readonly path: string;
142+
readonly content: string;
143+
}
144+
145+
const files: ReadonlyArray<SkillFile> = sources.flatMap(({ name, files }) =>
139146
Object.entries(files).map(([path, content]) => ({
140147
path: `${name}/${path}`,
141148
content,
@@ -155,7 +162,11 @@ const contentDigest = () => {
155162
* reader never sees half an install. Base64, so no content is read as shell
156163
* or rewritten by the local runtime's command-text path mapping.
157164
*/
158-
const installScript = (version: string) => {
165+
const installScript = (
166+
directory: string,
167+
files: ReadonlyArray<SkillFile>,
168+
version: string,
169+
) => {
159170
const writes = files
160171
.map(({ path, content }) => {
161172
const directory = path.slice(0, path.lastIndexOf("/"));
@@ -167,7 +178,7 @@ const installScript = (version: string) => {
167178
})
168179
.join("\n");
169180
return `set -eu
170-
root="$HOME/.local/share/dx/skills"
181+
root="$HOME/.local/share/dx/${directory}"
171182
stage="$root.stage.$$"
172183
old="$root.old.$$"
173184
rm -rf "$stage"
@@ -180,26 +191,53 @@ rm -rf "$old"
180191
`;
181192
};
182193

194+
/**
195+
* Replaces `~/.local/share/dx/<directory>` with the loaded files unless the
196+
* copy there is already `version`; `load` runs only when it is not. An
197+
* incomplete load is installed under another version, so the next turn
198+
* tries again.
199+
*/
200+
export const installSkillFiles = async (
201+
sandbox: Sandbox,
202+
directory: string,
203+
version: string,
204+
load: () => Promise<{
205+
readonly files: ReadonlyArray<SkillFile>;
206+
readonly complete: boolean;
207+
}>,
208+
) => {
209+
const root = `${DX_SHARE}/${directory}`;
210+
const installed = await sandbox
211+
.readFile(`${root}/${DIGEST_FILE}`)
212+
.catch(() => undefined);
213+
if (installed === version) return;
214+
const script = `${root}.install.sh`;
215+
const { files, complete } = await load();
216+
await sandbox.writeFile(
217+
script,
218+
installScript(directory, files, complete ? version : `${version}.partial`),
219+
);
220+
const result = await sandbox.exec(
221+
`bash ${script}; status=$?; rm -f ${script}; exit $status`,
222+
{ timeoutMs: 60_000 },
223+
);
224+
if (result.exitCode !== 0)
225+
throw new Error(result.stderr.trim() || "install script failed");
226+
};
227+
183228
/**
184229
* Writes the built-in skills into the workspace unless the copy there is
185230
* already this version. A failure is logged, not thrown: the instructions
186231
* still load, only their supporting files and scripts are missing.
187232
*/
188233
export const installBuiltInSkills = async (sandbox: Sandbox) => {
189-
const version = await contentDigest();
190234
try {
191-
const installed = await sandbox
192-
.readFile(`${BUILT_IN_SKILLS_DIRECTORY}/${DIGEST_FILE}`)
193-
.catch(() => undefined);
194-
if (installed === version) return;
195-
const script = `${BUILT_IN_SKILLS_DIRECTORY}.install.sh`;
196-
await sandbox.writeFile(script, installScript(version));
197-
const result = await sandbox.exec(
198-
`bash ${script}; status=$?; rm -f ${script}; exit $status`,
199-
{ timeoutMs: 60_000 },
235+
await installSkillFiles(
236+
sandbox,
237+
"skills",
238+
await contentDigest(),
239+
async () => ({ files, complete: true }),
200240
);
201-
if (result.exitCode !== 0)
202-
throw new Error(result.stderr.trim() || "install script failed");
203241
} catch (error) {
204242
builtInSkillsLogger.error("Built-in skills could not be installed.", {
205243
error: error instanceof Error ? error.message : String(error),

0 commit comments

Comments
 (0)