Skip to content

Commit 43c8dfd

Browse files
committed
Release v0.2.55
1 parent d963fd4 commit 43c8dfd

255 files changed

Lines changed: 16637 additions & 1868 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: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
{
2-
"version": "0.2.54",
2+
"version": "0.2.55",
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.54/dxd-linux-x64",
11-
"sha256": "6d2409188da917eaf4fbe03e7be6c25293442b5fa2901ba07c844e16b2adff5b"
10+
"url": "https://github.com/dxcode-dev/dxcode/releases/download/v0.2.55/dxd-linux-x64",
11+
"sha256": "5102e95a5f9535d897d463249d448cf5a5fa5133f5dfd7acd78410e0c24c7e0b"
1212
},
1313
"toolchain": {
1414
"node": ">=22.19.0",

‎alchemy.run.ts‎

Lines changed: 20 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -287,8 +287,6 @@ export default Alchemy.Stack(
287287
"",
288288
),
289289
packageDirectory: Config.String("DX_DEPLOYMENT_PACKAGE_DIR"),
290-
e2bTemplate: Config.String("DX_DEPLOYMENT_E2B_TEMPLATE"),
291-
e2bTemplateBuildId: Config.String("DX_DEPLOYMENT_E2B_TEMPLATE_BUILD_ID"),
292290
dxdChecksum: Config.String("DX_DEPLOYMENT_DXD_CHECKSUM"),
293291
dxdReleaseUrl: Config.String("DX_DEPLOYMENT_DXD_RELEASE_URL"),
294292
sourceShallowClone: Config.withDefault(
@@ -512,14 +510,17 @@ export default Alchemy.Stack(
512510
})).value
513511
: undefined
514512
: Option.getOrUndefined(configuration.exaApiKey);
515-
const selfhostProfiles =
516-
selfhost && e2bApiKey !== undefined
513+
// Every deployment that installs E2B builds its Orb templates from the
514+
// recipe at deploy time and rebuilds them when the recipe changes, as
515+
// OrbContainers does for Cloudflare. Staging and branch previews share
516+
// one E2B team, so they share one set, named for staging.
517+
const e2bProfiles =
518+
e2bApiKey !== undefined
517519
? yield* E2BProfiles("E2BProfiles", {
518520
apiKey: e2bApiKey,
519-
deploymentName: Option.getOrElse(
520-
configuration.deploymentName,
521-
() => "",
522-
),
521+
deploymentName: selfhost
522+
? Option.getOrElse(configuration.deploymentName, () => "")
523+
: "staging",
523524
recipeHash: yield* Effect.promise(() => e2bRecipeHash()),
524525
} as never)
525526
: undefined;
@@ -789,14 +790,10 @@ export default Alchemy.Stack(
789790
DX_AUTH_TRUSTED_ORIGINS: configuration.origin,
790791
...(portals === undefined ? {} : { DX_PORTAL_DOMAIN: portals.domain }),
791792
DX_ATTACHMENT_R2_ENDPOINT: attachmentSigning.endpoint,
792-
...(e2bInstalled
793+
...(e2bProfiles !== undefined
793794
? {
794-
DX_E2B_TEMPLATE:
795-
selfhostProfiles?.defaultTemplate ??
796-
`${configuration.e2bTemplate}-${defaultE2BOrbProfile.templateSuffix}`,
797-
DX_E2B_TEMPLATE_BUILD_ID:
798-
selfhostProfiles?.defaultBuildId ??
799-
configuration.e2bTemplateBuildId,
795+
DX_E2B_TEMPLATE: e2bProfiles.defaultTemplate,
796+
DX_E2B_TEMPLATE_BUILD_ID: e2bProfiles.defaultBuildId,
800797
DX_E2B_TIMEOUT_MS: "300000",
801798
}
802799
: {}),
@@ -816,30 +813,21 @@ export default Alchemy.Stack(
816813
DX_DXD_RELEASE_SHA256: configuration.dxdChecksum,
817814
DX_MIGRATION_MANIFEST_VERSION: String(migrationManifest.version),
818815
DX_RUNNER_PROFILE_CATALOG:
819-
selfhostProfiles === undefined
816+
e2bProfiles === undefined
820817
? runnerProfileCatalog(
821-
e2bInstalled
822-
? E2B_ORB_PROFILES.map((profile) => ({
818+
cloudflareInstalled
819+
? []
820+
: // No provider: the sizes people's own E2B teams run.
821+
E2B_ORB_PROFILES.map((profile) => ({
823822
...profile,
824823
adapter: "e2b",
825-
template: `${configuration.e2bTemplate}-${profile.templateSuffix}`,
824+
template: `dx-orb-${profile.templateSuffix}`,
826825
isolation: "sandbox",
827826
availability: "available",
828827
capabilities: deployedOrbCapabilities,
829-
}))
830-
: cloudflareInstalled
831-
? []
832-
: // No provider: the sizes people's own E2B teams run.
833-
E2B_ORB_PROFILES.map((profile) => ({
834-
...profile,
835-
adapter: "e2b",
836-
template: `dx-orb-${profile.templateSuffix}`,
837-
isolation: "sandbox",
838-
availability: "available",
839-
capabilities: deployedOrbCapabilities,
840-
})),
828+
})),
841829
)
842-
: Output.map(selfhostProfiles.profilesJson, (profilesJson) =>
830+
: Output.map(e2bProfiles.profilesJson, (profilesJson) =>
843831
runnerProfileCatalog(
844832
JSON.parse(profilesJson).map(
845833
(profile: Record<string, unknown>) => ({

‎apps/core/migration-manifest.json‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,11 @@
148148
"filename": "0027_release_v0.2.54.sql",
149149
"sha256": "5956ea9287461a35b334e56a74bb8e2b829d40eb2029bd744fc5fca3f9a45515",
150150
"classification": "non-destructive"
151+
},
152+
{
153+
"filename": "0028_release_v0.2.55.sql",
154+
"sha256": "d45cd1320779ae6cdfb1f9c2a2ba2453641519d945452ff3c643e4067ba1d215",
155+
"classification": "non-destructive"
151156
}
152157
],
153158
"durableObjects": [
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
-- Deleting a workspace (#1158) is a soft delete. The organization row stays,
2+
-- so projects, audit rows, and usage still point at it, but it has no
3+
-- members and its short name is released. Its projects are soft deleted
4+
-- too: a deleted project leaves every list and picker, and its name can be
5+
-- used again. A Thread in a deleted project stays with its owner, archived
6+
-- and read-only.
7+
ALTER TABLE organization ADD COLUMN deletedAt TEXT;
8+
9+
ALTER TABLE projects ADD COLUMN deleted_at TEXT;
10+
11+
-- Project reads never see a deleted project.
12+
DROP VIEW project_read_model;
13+
CREATE VIEW project_read_model AS
14+
SELECT projects.id, projects.owner_user_id, projects.workspace_id, projects.name,
15+
projects.description, projects.icon_key, projects.revision,
16+
project_repository.provider AS repository_provider,
17+
project_repository.binding_revision AS repository_binding_revision,
18+
project_repository.full_name AS repository_full_name,
19+
project_repository.web_url AS repository_web_url,
20+
project_repository.clone_url AS repository_clone_url,
21+
projects.ship_action, projects.commit_author_preference,
22+
projects.commit_author_name, projects.commit_author_email,
23+
projects.signing_preference, projects.runner_profile_id,
24+
projects.public_code_enabled, projects.created_at, projects.updated_at,
25+
(SELECT json_group_array(json_object(
26+
'provider', additional.provider, 'fullName', additional.full_name,
27+
'webUrl', additional.web_url, 'cloneUrl', additional.clone_url))
28+
FROM (SELECT * FROM project_additional_repository
29+
WHERE project_id = projects.id ORDER BY position) AS additional
30+
) AS additional_repositories_json,
31+
projects.deleted_at
32+
FROM projects LEFT JOIN project_repository ON project_repository.project_id = projects.id
33+
WHERE projects.deleted_at IS NULL;
34+
35+
DROP INDEX projects_personal_name_unique_idx;
36+
CREATE UNIQUE INDEX projects_personal_name_unique_idx
37+
ON projects(owner_user_id, name COLLATE NOCASE)
38+
WHERE workspace_id IS NULL AND name != 'No Project' AND deleted_at IS NULL;
39+
40+
DROP INDEX projects_workspace_name_unique_idx;
41+
CREATE UNIQUE INDEX projects_workspace_name_unique_idx
42+
ON projects(workspace_id, name COLLATE NOCASE)
43+
WHERE workspace_id IS NOT NULL AND name != 'No Project' AND deleted_at IS NULL;
44+
45+
DROP INDEX projects_projectless_personal_unique_idx;
46+
CREATE UNIQUE INDEX projects_projectless_personal_unique_idx
47+
ON projects(owner_user_id)
48+
WHERE workspace_id IS NULL AND name = 'No Project' AND deleted_at IS NULL;
49+
50+
DROP INDEX projects_projectless_workspace_unique_idx;
51+
CREATE UNIQUE INDEX projects_projectless_workspace_unique_idx
52+
ON projects(owner_user_id, workspace_id)
53+
WHERE workspace_id IS NOT NULL AND name = 'No Project' AND deleted_at IS NULL;
54+
55+
-- The owner may hand the workspace to another member: the new owner is
56+
-- promoted first, then the old owner steps down. A workspace never loses
57+
-- its last owner.
58+
DROP TRIGGER workspace_owner_role_immutable_before_update;
59+
CREATE TRIGGER workspace_owner_role_immutable_before_update
60+
BEFORE UPDATE OF role ON member
61+
WHEN OLD.role = 'owner' AND NEW.role <> 'owner'
62+
AND (
63+
SELECT count(*)
64+
FROM member
65+
WHERE organizationId = OLD.organizationId
66+
AND role = 'owner'
67+
) <= 1
68+
BEGIN
69+
SELECT RAISE(ABORT, 'workspace owner is immutable');
70+
END;
71+
72+
-- What a Thread asks of its owner, listed in their Needs-you inbox. The
73+
-- agent's request_attention tool writes a 'question' or an 'approval' with a
74+
-- one-line ask and an optional link; a root Thread's failed turn writes
75+
-- 'failed' when nothing else is asked. The Thread's next turn starting, or
76+
-- the owner's Dismiss, deletes the row.
77+
CREATE TABLE thread_attention_request (
78+
thread_id TEXT PRIMARY KEY REFERENCES threads (id) ON DELETE CASCADE,
79+
kind TEXT NOT NULL CHECK (kind IN ('question', 'approval', 'failed')),
80+
summary TEXT CHECK (summary IS NULL OR length(summary) BETWEEN 1 AND 300),
81+
url TEXT CHECK (url IS NULL OR length(url) BETWEEN 1 AND 2048),
82+
requested_at TEXT NOT NULL CHECK (length(requested_at) BETWEEN 20 AND 40)
83+
);
84+
85+
-- The GitHub issues and pull requests a Thread works on. A comment
86+
-- subscription on one links it, as does the agent's `link_github_issue`
87+
-- tool; the link outlives the subscription. The Thread header lists them,
88+
-- and find_thread's `issue:owner/repo#N` finds the Thread from one.
89+
-- Webhooks keep the title and state current; no view reads GitHub.
90+
CREATE TABLE thread_github_link (
91+
thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
92+
-- owner/name as GitHub spells it; repository_key is it lowercased.
93+
repository TEXT NOT NULL CHECK (length(repository) BETWEEN 3 AND 200),
94+
repository_key TEXT NOT NULL CHECK (repository_key = lower(repository)),
95+
number INTEGER NOT NULL CHECK (number > 0),
96+
kind TEXT NOT NULL CHECK (kind IN ('issue', 'pull')),
97+
subject_json TEXT CHECK (
98+
subject_json IS NULL
99+
OR (json_valid(subject_json) AND length(subject_json) <= 2000)
100+
),
101+
created_at TEXT NOT NULL,
102+
PRIMARY KEY (thread_id, repository_key, number)
103+
);
104+
105+
CREATE INDEX thread_github_link_target_idx
106+
ON thread_github_link (repository_key, number);
107+
108+
-- Threads that already follow an issue's or a PR's comments.
109+
INSERT OR IGNORE INTO thread_github_link (
110+
thread_id, repository, repository_key, number, kind, subject_json,
111+
created_at
112+
)
113+
SELECT thread_id, repository, lower(repository),
114+
CAST(json_extract(filter_json,
115+
CASE filter_kind WHEN 'pr-comments' THEN '$.prNumber'
116+
ELSE '$.issueNumber' END) AS INTEGER),
117+
CASE filter_kind WHEN 'pr-comments' THEN 'pull' ELSE 'issue' END ,
118+
subject_json,
119+
created_at
120+
FROM github_subscription
121+
WHERE destination = 'this-thread'
122+
AND filter_kind IN ('pr-comments', 'issue-comments')
123+
AND CAST(json_extract(filter_json,
124+
CASE filter_kind WHEN 'pr-comments' THEN '$.prNumber'
125+
ELSE '$.issueNumber' END) AS INTEGER) > 0;
126+
127+
-- "Add dx as co-author" (#1303). A project snapshots it at creation, like
128+
-- its commit author; when it is on and the person authors commits, each
129+
-- commit gets a `Co-authored-by: dxcodeagent <agent@dxcode.dev>` trailer.
130+
-- Existing projects keep crediting dx. Defaults store NULL until someone
131+
-- chooses, which resolves to on.
132+
ALTER TABLE projects
133+
ADD COLUMN dx_co_author INTEGER NOT NULL DEFAULT 1
134+
CHECK (dx_co_author IN (0, 1));
135+
136+
ALTER TABLE personal_project_defaults
137+
ADD COLUMN dx_co_author INTEGER
138+
CHECK (dx_co_author IS NULL OR dx_co_author IN (0, 1));
139+
140+
ALTER TABLE workspace_project_defaults
141+
ADD COLUMN dx_co_author INTEGER
142+
CHECK (dx_co_author IS NULL OR dx_co_author IN (0, 1));
143+
144+
DROP VIEW project_read_model;
145+
CREATE VIEW project_read_model AS
146+
SELECT projects.id, projects.owner_user_id, projects.workspace_id, projects.name,
147+
projects.description, projects.icon_key, projects.revision,
148+
project_repository.provider AS repository_provider,
149+
project_repository.binding_revision AS repository_binding_revision,
150+
project_repository.full_name AS repository_full_name,
151+
project_repository.web_url AS repository_web_url,
152+
project_repository.clone_url AS repository_clone_url,
153+
projects.ship_action, projects.commit_author_preference,
154+
projects.commit_author_name, projects.commit_author_email,
155+
projects.signing_preference, projects.runner_profile_id,
156+
projects.public_code_enabled, projects.created_at, projects.updated_at,
157+
(SELECT json_group_array(json_object(
158+
'provider', additional.provider, 'fullName', additional.full_name,
159+
'webUrl', additional.web_url, 'cloneUrl', additional.clone_url))
160+
FROM (SELECT * FROM project_additional_repository
161+
WHERE project_id = projects.id ORDER BY position) AS additional
162+
) AS additional_repositories_json,
163+
projects.deleted_at,
164+
projects.dx_co_author
165+
FROM projects LEFT JOIN project_repository ON project_repository.project_id = projects.id
166+
WHERE projects.deleted_at IS NULL;
167+
168+
-- The person's desktop sidebar panel, shared by every device they sign in on
169+
-- and returned with their sidebar layout (`sidebar_layout_entry`): the width
170+
-- they dragged it to (NULL until they resize it, so the default applies) and
171+
-- whether they keep it collapsed. Without a row the sidebar opens at its
172+
-- default width; a width the current schema no longer accepts falls back to
173+
-- the default too.
174+
CREATE TABLE sidebar_panel (
175+
user_id TEXT PRIMARY KEY NOT NULL,
176+
width INTEGER CHECK (width IS NULL OR width BETWEEN 1 AND 4096),
177+
collapsed INTEGER NOT NULL DEFAULT 0 CHECK (collapsed IN (0, 1)),
178+
updated_at TEXT NOT NULL
179+
) WITHOUT ROWID;
180+
181+
-- When E2B refuses a Thread's create with a client error it makes no
182+
-- sandbox, and the resolver returns the attempt to 'uninitialized' so the
183+
-- next request creates again. The transition trigger rejected that step, so
184+
-- the record stayed 'provisioning' with no sandbox and every later request
185+
-- failed. Allow it only back to an empty record.
186+
DROP TRIGGER execution_workspace_transition_before_update;
187+
188+
CREATE TRIGGER execution_workspace_transition_before_update
189+
BEFORE UPDATE ON execution_workspace
190+
WHEN NEW.state IS NOT OLD.state
191+
AND NOT (
192+
(OLD.state = 'uninitialized' AND NEW.state = 'provisioning')
193+
OR (OLD.state = 'provisioning' AND NEW.state IN ('initialized', 'conflict'))
194+
OR (OLD.state = 'provisioning' AND NEW.state = 'uninitialized'
195+
AND OLD.provider_sandbox_id IS NULL
196+
AND NEW.provider_sandbox_id IS NULL
197+
AND NEW.initialization_attempt_id IS NULL)
198+
OR (OLD.state = 'legacy' AND NEW.state IN ('initialized', 'legacy_unavailable', 'conflict'))
199+
OR (OLD.state = 'initialized' AND NEW.state = 'lost')
200+
)
201+
BEGIN
202+
SELECT RAISE(ABORT, 'invalid execution workspace transition');
203+
END;

‎apps/core/package.json‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@dx/core",
33
"private": true,
4-
"version": "0.2.54",
4+
"version": "0.2.55",
55
"license": "FSL-1.1-ALv2",
66
"type": "module",
77
"scripts": {
@@ -20,7 +20,7 @@
2020
"@dx/api": "workspace:^",
2121
"@dx/domain": "workspace:^",
2222
"@effect/sql-d1": "4.0.5",
23-
"@flue/runtime": "file:../../third-party/flue/runtime-de1dda9ae8b4791dea431cbdcbc818f4b5f22f71.tgz",
23+
"@flue/runtime": "file:../../third-party/flue/runtime-c3d40fde8d0711a62d0d7cb7b99ccec8dbba5552.tgz",
2424
"@jitl/quickjs-wasmfile-release-sync": "0.32.0",
2525
"@logtape/logtape": "2.3.1",
2626
"@modelcontextprotocol/client": "2.0.0",
@@ -43,8 +43,8 @@
4343
"@cloudflare/vitest-plugin": "1.0.0",
4444
"@cloudflare/workers-types": "5.20260820.1",
4545
"@earendil-works/pi-ai": "1.1.0",
46-
"@flue/sdk": "file:../../third-party/flue/sdk-de1dda9ae8b4791dea431cbdcbc818f4b5f22f71.tgz",
47-
"@flue/vite": "file:../../third-party/flue/vite-de1dda9ae8b4791dea431cbdcbc818f4b5f22f71.tgz",
46+
"@flue/sdk": "file:../../third-party/flue/sdk-c3d40fde8d0711a62d0d7cb7b99ccec8dbba5552.tgz",
47+
"@flue/vite": "file:../../third-party/flue/vite-c3d40fde8d0711a62d0d7cb7b99ccec8dbba5552.tgz",
4848
"@msw/cloudflare": "^0.0.1",
4949
"@types/ws": "8.18.1",
5050
"@valibot/to-json-schema": "1.7.1",

‎apps/core/src/agents/built-in-skills/orb-setup/skill.md‎

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ its Orb lifecycle files.
1313
| File | When it runs | Purpose |
1414
|------|--------------|---------|
1515
| `.agents/setup` | Once for each new Thread's Orb, after dx checks out the repository and before the agent's first turn | Install toolchains, dependencies, databases, and other prerequisites |
16-
| `.agents/resume` | After setup on first activation, and again each time a paused Orb is activated | Fast, idempotent authentication, reconnect, or repair steps |
16+
| `.agents/resume` | Once after setup on first activation, once each time a paused Orb wakes, and when the Project's environment variables change | Fast, idempotent authentication, reconnect, or repair steps |
1717
| `.dx/services.yaml` | When the user opens a Portal URL whose service is stopped | Start long-running dev servers and other services on demand |
1818

1919
Key invariants:
@@ -28,15 +28,14 @@ Key invariants:
2828
the setup file's contents, and does not run it again for that Thread unless one of them changes.
2929
A failed or timed-out setup is logged and retried on the next activation; it never blocks the
3030
Thread. The workspace is usable without it, so the agent can finish setup work by hand.
31-
- Each hook run is limited to 5 minutes. dx also runs `.agents/resume` when it reconnects the
32-
workspace daemon and stops that run after 120 seconds. Resume can therefore run more than once
33-
per wake: keep it idempotent and finish within seconds; never install dependencies there.
34-
- Hooks run at the repository root as `user` with a minimal environment: `PATH`, `HOME`, `USER`,
35-
`LOGNAME`, `SHELL`, and `GIT_TERMINAL_PROMPT=0`. Setup gets no GitHub token, no source
31+
- Setup is limited to 5 minutes. The workspace daemon runs `.agents/resume` and stops it after
32+
120 seconds; the agent's turn does not wait for it. A daemon restart also runs it, so keep it
33+
idempotent and finish within seconds; never install dependencies there.
34+
- Hooks run at the repository root as `user`. Setup has a minimal environment: `PATH`, `HOME`,
35+
`USER`, `LOGNAME`, `SHELL`, and `GIT_TERMINAL_PROMPT=0`. Setup gets no GitHub token, no source
3636
credentials, and none of the Project's environment variables or secrets. Never authenticate a
3737
user or service during setup. Install its client in setup and authenticate it during resume or
38-
when the agent first needs it; the resume run by the workspace daemon receives the Project's
39-
environment variables.
38+
when the agent first needs it; resume receives the Project's environment variables.
4039
- Each hook's full output goes to its log under `~/.cache/dx/logs` (`setup.log`, `resume.log`),
4140
replaced on each run. Only the exit code decides whether the hook succeeded.
4241
- Orbs are Debian sandboxes with passwordless `sudo`. The standard image has Node.js 24 with

0 commit comments

Comments
 (0)