Merge branch 'main' into nightly
No files matched your search
@@ -8,7 +8,30 @@ When an agent picks up a task that involves working with code or files, it needs
|
||||
|
||||
Execution workspaces exist so that multiple agents can work on the same project simultaneously without stepping on each other. When a project is configured for isolated work, agents can get their own copy — their own branch, folder, and runtime context — so work in one workspace doesn't accidentally break another.
|
||||
|
||||
This guide covers what execution workspaces are, how they relate to projects, and how to use the workspace detail screen — its three tabs, runtime controls, and logs — to inspect and manage them.
|
||||
This guide covers what execution workspaces are, how they relate to projects, and how to use the workspace detail screen — its tabs, runtime controls, and logs — to inspect and manage them.
|
||||
|
||||
---
|
||||
|
||||
## Before you start: turning workspaces on
|
||||
|
||||
Workspaces are an **experimental** feature, so the **Workspaces** screen is hidden until you opt in.
|
||||
|
||||
1. Go to **Instance settings → Experimental**.
|
||||
2. Turn on **Enable Isolated Workspaces**.
|
||||
|
||||
Once it's on, a **Workspaces** item appears in the sidebar, and a **Workspaces** tab appears on any project that has workspace activity. With the setting off, the sidebar item isn't shown and visiting `/workspaces` directly just sends you back to your issues.
|
||||
|
||||
> **Note:** This is the single switch that exposes all the workspace UI described in this guide — execution-workspace controls in project configuration, the Workspaces screen, and the per-issue workspace mode picker. If you're not using isolated workspaces, leaving it off keeps that UI out of your way.
|
||||
|
||||
### What the Workspaces screen is — and isn't
|
||||
|
||||
The most common surprise is opening **Workspaces** for the first time and finding it empty, with nothing to click. That's expected, and it's worth understanding why:
|
||||
|
||||
- **You don't create workspaces here.** There's no "New workspace" button. Execution workspaces are **provisioned automatically** the first time an agent picks up a task that needs one (see the next section). The Workspaces screen is for *inspecting and managing* what already exists — not for creating anything by hand.
|
||||
- **It stays empty until work happens.** If no agent has run an isolated task yet, you'll see **"No workspace activity yet."** That's not a bug — there's genuinely nothing to show. Assign a task to an agent, let it run in an isolated workspace, and the workspace will appear here on its own.
|
||||
- **A project only shows up once it has something to show.** A project's primary workspace surfaces as soon as the project has one set; other (non-primary) project workspaces surface once they have runtime services or config; and isolated execution workspaces surface once a task has run against them. A freshly created project with none of that won't list anything yet — and its **Workspaces** tab stays hidden until it does.
|
||||
|
||||
So if the screen looks bare, the fix is usually "run a task," not "find the hidden create button" — there isn't one.
|
||||
|
||||
---
|
||||
|
||||
@@ -23,8 +46,6 @@ When a task runs, Paperclip resolves which execution workspace to use for that r
|
||||
3. **The agent receives the workspace path** and works within it
|
||||
4. **The workspace persists** after the run if you're using an isolated or reusable workspace mode
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Workspace modes
|
||||
@@ -116,29 +137,54 @@ You'll want to pay closer attention when:
|
||||
|
||||
## Workspace list
|
||||
|
||||
All execution workspaces attached to a project are listed on that project's **Workspaces** tab. Open a project, click **Workspaces**, and you'll see every workspace Paperclip has provisioned — isolated worktrees, reused workspaces, and the project's primary checkout.
|
||||
There are two places to see your workspaces, and they show the same data from different angles:
|
||||
|
||||
Each entry shows:
|
||||
- **The Workspaces sidebar item** opens the company-wide view. It's grouped by project, so you see every project that has workspace activity, each with its workspaces underneath. Projects with running services float to the top.
|
||||
- **A project's Workspaces tab** (open a project, click **Workspaces**) is the same list scoped to that one project. It only appears once that project has workspace activity.
|
||||
|
||||
- The workspace **name** (and the issue it was originally provisioned for, if any)
|
||||
- The **mode** — isolated, reuse, or project primary
|
||||
- The **provider type** — for example, git worktree or local folder
|
||||
- The **status** — active, archived, or cleanup_failed
|
||||
- When it was opened and last used
|
||||
Either way, you'll see the project's primary workspace and any isolated execution workspaces Paperclip has provisioned. If nothing has been provisioned yet, the company-wide view shows **"No workspace activity yet."**
|
||||
|
||||
Click a workspace name to open its detail screen. From there, you can inspect and edit its configuration, control its runtime services, review its operation logs, and see every issue that has run against it.
|
||||

|
||||
|
||||

|
||||
Each workspace appears as a card showing:
|
||||
|
||||
The header of the detail screen repeats the mode, provider type, and status as small pills so you can tell at a glance which kind of workspace you're looking at. Above the tabs, a **Workspace commands** panel shows the source of the workspace's runtime config — whether it's inheriting from the project workspace, overriding it at the execution workspace level, or has no runtime config defined yet. From that same panel you can start services, stop them, restart them, or fire off one-shot jobs.
|
||||
- Its **kind** — *Execution workspace* or *Project workspace*
|
||||
- The workspace **name**, with its **branch** and **path**
|
||||
- Running **services** (for example, `1/1 services`) and a live link when a service exposes a URL
|
||||
- For an execution workspace, its **status** (active, idle, in review, archived, or cleanup failed) and a **Close workspace** / **Retry close** control
|
||||
- The **linked tasks** that have run against it, and when it was last updated
|
||||
|
||||
The detail screen is split into three tabs:
|
||||
If a workspace has runtime config, the card also gives you a **Start services** / **Stop services** button right there. Click the workspace name to open its detail screen, where you can inspect and edit its configuration, control its runtime services, review its operation logs, and see every task that has run against it.
|
||||
|
||||
1. **Configuration** — workspace settings, workspace context, and concrete location
|
||||
2. **Runtime logs** — history of runtime and cleanup operations
|
||||
3. **Issues** — every issue that has used this workspace
|
||||
The header of the detail screen shows the workspace name with quick start/stop/restart controls for its services on the right. Below the header, the detail screen is split into tabs:
|
||||
|
||||
The tabs remember your last selection per workspace, so if you always land on Runtime logs for a specific workspace, Paperclip keeps taking you there.
|
||||
1. **Tasks** — every task that has run against this workspace (this is the default tab)
|
||||
2. **Services** — the runtime services and one-shot jobs for this workspace, with start/stop/restart and run controls
|
||||
3. **Configuration** — workspace settings, workspace context, and concrete location
|
||||
4. **Runtime logs** — history of runtime and cleanup operations
|
||||
5. **Routines** — routines that use workspace-specific variables, which you can run against this workspace
|
||||
|
||||
Installed plugins can add their own tabs alongside these (for example, a workspace-diff plugin contributes a **Changes** tab). The tabs remember your last selection per workspace, so if you always land on Runtime logs for a specific workspace, Paperclip keeps taking you there.
|
||||
|
||||
---
|
||||
|
||||
## Services tab
|
||||
|
||||
The **Services** tab is the control surface for runtime behaviour: services you want running in the background, and one-shot jobs you want to fire off against this workspace. (The header above the tabs mirrors the service controls as quick start/stop/restart buttons, so you can act on them from any tab.)
|
||||
|
||||

|
||||
|
||||
Two rules govern what you can do here:
|
||||
|
||||
1. **You need a working directory.** Every command Paperclip runs against the workspace needs an absolute path to run in. If the workspace's `cwd` is empty, the action buttons are disabled and a hint reminds you to fix that first. Fill in the **Working directory** field on the Configuration tab, save, and the controls light up again.
|
||||
2. **Services need runtime config.** Service commands only show up if there's a runtime config somewhere in the chain — either inherited from the project workspace or overridden on this execution workspace. If neither exists, the services list stays empty with a short note; jobs may still run if they're defined and a `cwd` is set.
|
||||
|
||||
The tab groups commands into two sections:
|
||||
|
||||
- **Services** — long-running processes like `pnpm dev`, a Postgres container, or a build watcher. For each service you'll see start, stop, and restart controls and the service's last observed status.
|
||||
- **Jobs** — one-shot commands like `pnpm db:migrate` or a test runner. Each job has a single run action.
|
||||
|
||||
When Paperclip is busy running an action (for example, starting a service), the controls show a pending state and block duplicate actions. On success you'll see a short confirmation — "Workspace service started.", "Workspace job completed." — and on failure, the error message from the adapter appears in red. Either way, the full history ends up in the Runtime logs tab.
|
||||
|
||||
---
|
||||
|
||||
@@ -148,22 +194,6 @@ The Configuration tab is where you inspect and edit everything that makes this w
|
||||
|
||||

|
||||
|
||||
### Workspace commands panel
|
||||
|
||||
Above the tabs, and visible regardless of which tab you're on, sits the **Workspace commands** panel. It's the single control surface for runtime behaviour: services you want running in the background, and one-shot jobs you want to fire off against this workspace.
|
||||
|
||||
Two rules govern this panel:
|
||||
|
||||
1. **You need a working directory.** Every command Paperclip runs against the workspace needs an absolute path to run in. If the workspace's `cwd` is empty, the action buttons are disabled and a hint reminds you to fix that first. Fill in the **Working directory** field on the Configuration tab, save, and the controls light up again.
|
||||
2. **Services need runtime config.** Service commands only show up if there's a runtime config somewhere in the chain — either inherited from the project workspace or overridden on this execution workspace. If neither exists, the services panel stays empty with a short note; jobs may still run if they're defined and a `cwd` is set.
|
||||
|
||||
The panel groups commands into two sections:
|
||||
|
||||
- **Services** — long-running processes like `pnpm dev`, a Postgres container, or a build watcher. For each service you'll see start, stop, and restart controls and the service's last observed status.
|
||||
- **Jobs** — one-shot commands like `pnpm db:migrate` or a test runner. Each job has a single run action.
|
||||
|
||||
When Paperclip is busy running an action (for example, starting a service), the panel shows a pending state and blocks duplicate actions. On success you'll see a short confirmation — "Workspace service started.", "Workspace job completed." — and on failure, the error message from the adapter appears in red. Either way, the full history ends up in the Runtime logs tab.
|
||||
|
||||
### Workspace settings
|
||||
|
||||
The **Workspace settings** card holds the editable fields for the workspace. Most of these are populated when Paperclip provisions the workspace; you usually only need to touch them when an agent or project has moved, a branch was renamed, or the provisioning scripts changed.
|
||||
@@ -233,7 +263,7 @@ The **Runtime config source** notice on the Configuration tab tells you which of
|
||||
|
||||
- **Execution workspace override** — this workspace has its own runtime config and ignores whatever the project workspace provides. Use this when a specific worktree needs a different port, a different dev command, or extra jobs that don't belong on every workspace.
|
||||
- **Project workspace default** — this workspace is inheriting the project's runtime config as-is. This is the common case and the one you should aim for — overrides add drift and should be the exception.
|
||||
- **None** — neither level has runtime config defined, so Paperclip can't start services. Jobs may still run if they're provided, but services won't appear in the Workspace commands panel.
|
||||
- **None** — neither level has runtime config defined, so Paperclip can't start services. Jobs may still run if they're provided, but services won't appear in the Services tab.
|
||||
|
||||
If you've added an override and want to go back to inheriting, click **Reset to inherit** — it clears the override and the workspace falls back to the project workspace's config. If the project workspace has no runtime config of its own, that button is disabled; you'd otherwise be resetting the execution workspace to "None".
|
||||
|
||||
@@ -261,7 +291,7 @@ Under the **Recent operations** heading, each entry represents one workspace ope
|
||||
|
||||
This tab covers both kinds of operations:
|
||||
|
||||
- **Runtime operations** — service starts, stops, restarts, and one-shot jobs you trigger from the Workspace commands panel at the top of the detail page.
|
||||
- **Runtime operations** — service starts, stops, restarts, and one-shot jobs you trigger from the Services tab (or the header quick controls).
|
||||
- **Cleanup operations** — provision, teardown, and cleanup command runs that happen when Paperclip prepares or closes the workspace.
|
||||
|
||||
If nothing has happened yet, you'll see "No workspace operations have been recorded yet." When the operation log fails to load, the error message from the server is shown in red.
|
||||
@@ -272,7 +302,7 @@ Use this tab as your first stop when an agent reports that a service didn't come
|
||||
|
||||
If you're new to the Runtime logs tab, it helps to know which UI actions produce which operation entries:
|
||||
|
||||
- Clicking **Start** on a service in the Workspace commands panel creates an operation with the service's command, a `started` phase, and — on a successful start — a running or finished status depending on whether the service stays alive or exits immediately.
|
||||
- Clicking **Start** on a service in the Services tab creates an operation with the service's command, a `started` phase, and — on a successful start — a running or finished status depending on whether the service stays alive or exits immediately.
|
||||
- Clicking **Stop** or **Restart** on a service produces a short operation that records how the service was terminated or bounced.
|
||||
- Clicking **Run** on a job produces a single operation with the job's command; its final status reflects the job's exit code.
|
||||
- **Close workspace** (from the Configuration tab) runs the cleanup command and then the teardown command in sequence. Each appears as its own operation so you can tell which of the two failed if the close ends in `cleanup_failed`.
|
||||
@@ -282,11 +312,11 @@ If nothing about a failure is obvious from the excerpt, remember that excerpts a
|
||||
|
||||
---
|
||||
|
||||
## Issues tab
|
||||
## Tasks tab
|
||||
|
||||
The Issues tab shows every issue that has ever run against this execution workspace — not just the source issue that created it, but any follow-ups, retries, and related tasks that inherited the workspace.
|
||||
The Tasks tab shows every issue that has ever run against this execution workspace — not just the source issue that created it, but any follow-ups, retries, and related tasks that inherited the workspace.
|
||||
|
||||

|
||||

|
||||
|
||||
The list uses the same components and filters as the main issues view, so you get:
|
||||
|
||||
@@ -305,10 +335,10 @@ If an issue in the list needs to be reassigned or rescheduled before you can clo
|
||||
|
||||
### Source issue vs. linked issues
|
||||
|
||||
Two different relationships show up on the Issues tab and in the Configuration tab's Workspace context card, and it's worth keeping them straight:
|
||||
Two different relationships show up on the Tasks tab and in the Configuration tab's Workspace context card, and it's worth keeping them straight:
|
||||
|
||||
- The **source issue** is the single issue that originally caused Paperclip to provision this workspace. It's stored on the workspace itself and appears in the Workspace context card. For isolated worktrees created from a specific task, this will typically be the task that kicked the whole flow off.
|
||||
- The **linked issues** listed on the Issues tab are every issue that has ever been attached to this workspace, including the source issue, follow-up child issues that inherited the workspace, and any issue whose `inheritExecutionWorkspaceFromIssueId` pointed back here.
|
||||
- The **linked issues** listed on the Tasks tab are every issue that has ever been attached to this workspace, including the source issue, follow-up child issues that inherited the workspace, and any issue whose `inheritExecutionWorkspaceFromIssueId` pointed back here.
|
||||
|
||||
When you're deciding whether it's safe to archive, look at the full linked list, not just the source issue. An archived workspace takes all its branch state with it, and a follow-up issue that still references it will end up without a valid cwd the next time it runs.
|
||||
|
||||
@@ -326,7 +356,7 @@ If both succeed, the workspace transitions to `archived` and disappears from the
|
||||
Some practical tips:
|
||||
|
||||
- Before closing, check the Runtime logs tab for anything still running. Services that are `running` will be stopped as part of cleanup, but jobs that are genuinely in-flight will also be killed — make sure you're not about to interrupt something important.
|
||||
- Review the **Issues tab** for any issue still in `in_progress` that's tied to this workspace. Archive the workspace only after those issues are done, cancelled, or moved to another workspace.
|
||||
- Review the **Tasks tab** for any issue still in `in_progress` that's tied to this workspace. Archive the workspace only after those issues are done, cancelled, or moved to another workspace.
|
||||
- If an agent had uncommitted changes on the branch, those changes go away at teardown. Either merge the branch first or stash/commit anything you want to keep.
|
||||
|
||||
The **Cleanup** row on the Concrete location card tells you whether Paperclip has already scheduled the workspace for automatic cleanup — for example, because it's been idle past a retention window — and what the reason is. If you see a scheduled cleanup you didn't expect, look at the project's workspace lifecycle settings to understand why.
|
||||
@@ -379,14 +409,14 @@ Isolation pays off when agents are genuinely making code changes you'll want to
|
||||
|
||||
## Observability at a glance
|
||||
|
||||
Pulling together the three tabs, here's how the detail screen maps to typical questions you'll ask:
|
||||
Pulling together the tabs, here's how the detail screen maps to typical questions you'll ask:
|
||||
|
||||
- "What is this workspace and where does it live?" → Configuration tab, the Concrete location card.
|
||||
- "Why does this workspace exist and what's it attached to?" → Configuration tab, the Workspace context card.
|
||||
- "Who decides how services run here?" → Configuration tab, the Runtime config source notice.
|
||||
- "What has actually happened in this workspace?" → Runtime logs tab.
|
||||
- "Who's been running against this workspace?" → Issues tab.
|
||||
- "Is anything running right now?" → Workspace commands panel at the top (service status) plus Issues tab (live-run indicator).
|
||||
- "Who's been running against this workspace?" → Tasks tab.
|
||||
- "Is anything running right now?" → Services tab (or the header quick controls) for service status plus Tasks tab (live-run indicator).
|
||||
|
||||
The breadcrumbs at the top of the screen also tie everything back together: **Projects → {project name} → Workspaces → {workspace name}**. From any workspace detail screen, you're always one click from the project and two from the workspace list.
|
||||
|
||||
@@ -410,14 +440,14 @@ A few concrete patterns show up often enough to be worth calling out:
|
||||
|
||||
**Shipping a migration**
|
||||
1. Create a job in the workspace's runtime config (for example, `db:migrate`).
|
||||
2. Open the workspace and click **Run** on that job from the Workspace commands panel.
|
||||
2. Open the workspace and click **Run** on that job from the Services tab.
|
||||
3. Wait for the operation to appear in Runtime logs with a successful status.
|
||||
4. Link the resulting evidence to the issue — the operation timestamp is enough.
|
||||
|
||||
**Reusing a workspace across tasks**
|
||||
1. When creating the follow-up issue, set `parentId` if it's a child task, or `inheritExecutionWorkspaceFromIssueId` if it's a sibling that should share the same workspace.
|
||||
2. Paperclip will wire the new issue up to the existing workspace on checkout.
|
||||
3. On the Issues tab of the workspace, you'll see the new issue alongside the original.
|
||||
3. On the Tasks tab of the workspace, you'll see the new issue alongside the original.
|
||||
|
||||
These patterns are just defaults — adapt them to how your team actually operates.
|
||||
|
||||
@@ -425,7 +455,7 @@ These patterns are just defaults — adapt them to how your team actually operat
|
||||
|
||||
## You're set
|
||||
|
||||
Execution workspaces give agents the isolated, stateful environments they need to do reliable file-based work. The Workspace list shows you everything provisioned for a project; the Configuration tab keeps the path, repo, and runtime config in sync with reality; the Runtime logs tab tells you what actually ran; and the Issues tab shows you who's been using it.
|
||||
Execution workspaces give agents the isolated, stateful environments they need to do reliable file-based work. The Workspace list shows you everything provisioned for a project; the Configuration tab keeps the path, repo, and runtime config in sync with reality; the Runtime logs tab tells you what actually ran; and the Tasks tab shows you who's been using it.
|
||||
|
||||
The next guide covers heartbeats and routines — how you decide *when* an agent runs, and why most agents should stay dormant until real work arrives.
|
||||
|
||||
|
||||
@@ -88,7 +88,8 @@ Placeholders used below:
|
||||
- `{goalId}` → Launch v1 goal id
|
||||
- `{routineId}` → demo routine id
|
||||
- `{issueId}` → demo issue id with chat content
|
||||
- `{workspaceId}` → execution workspace id bound to that issue
|
||||
- `{workspaceId}` → project primary workspace id
|
||||
- `{executionWorkspaceId}` → isolated execution workspace id (DB-seeded for detail-page shots)
|
||||
- `{cliAuthId}` → code id from `/cli-auth`
|
||||
- `{boardClaimToken}` → token from board claim link
|
||||
|
||||
@@ -165,9 +166,10 @@ Costs tabs live in React state (not URL/hash). Capture each tab by clicking its
|
||||
|
||||
| Filename | URL | Notes |
|
||||
|---|---|---|
|
||||
| `configuration.png` | `/{slug}/execution-workspaces/{workspaceId}/configuration` | Default tab. Shows cwd / repo fields. |
|
||||
| `runtime-logs.png` | `/{slug}/execution-workspaces/{workspaceId}/runtime-logs` | At least one log entry from a recent run. |
|
||||
| `issues.png` | `/{slug}/execution-workspaces/{workspaceId}/issues` | Issues linked to this workspace. |
|
||||
| `configuration.png` | `/{slug}/execution-workspaces/{executionWorkspaceId}/configuration` | Workspace settings, context, concrete location. |
|
||||
| `services.png` | `/{slug}/execution-workspaces/{executionWorkspaceId}/services` | Runtime services + jobs with controls. |
|
||||
| `runtime-logs.png` | `/{slug}/execution-workspaces/{executionWorkspaceId}/runtime-logs` | Operation log rows (provision, service start, failed job). |
|
||||
| `issues.png` | `/{slug}/execution-workspaces/{executionWorkspaceId}/issues` | The Tasks tab — issues linked to this workspace. |
|
||||
|
||||
### Adapters — user (`screenshots/light/adapters/`)
|
||||
|
||||
|
||||
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 257 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 176 KiB |
|
Before Width: | Height: | Size: 148 KiB After Width: | Height: | Size: 208 KiB |
|
Before Width: | Height: | Size: 111 KiB After Width: | Height: | Size: 284 KiB |
|
After Width: | Height: | Size: 237 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 256 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 175 KiB |
|
Before Width: | Height: | Size: 147 KiB After Width: | Height: | Size: 207 KiB |
|
Before Width: | Height: | Size: 110 KiB After Width: | Height: | Size: 282 KiB |
|
After Width: | Height: | Size: 235 KiB |
@@ -983,17 +983,6 @@
|
||||
"ui/src/pages/OrgChart.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "dark/org/execution-workspaces-list.png",
|
||||
"theme": "dark",
|
||||
"viewport": "1440x900",
|
||||
"route": "/ACME/workspaces",
|
||||
"captured_against": "HEAD",
|
||||
"captured_sha": "911a1e8b0d24205acd5006e837a5c5e8c4bfb447",
|
||||
"depends_on": [
|
||||
"ui/src/pages/Workspaces.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "dark/org/org-chart-add-agent.png",
|
||||
"theme": "dark",
|
||||
@@ -1472,9 +1461,9 @@
|
||||
"file": "dark/workspaces/configuration.png",
|
||||
"theme": "dark",
|
||||
"viewport": "1440x900",
|
||||
"route": "/ACME/execution-workspaces/{workspaceId}/configuration",
|
||||
"captured_against": "HEAD",
|
||||
"captured_sha": "911a1e8b0d24205acd5006e837a5c5e8c4bfb447",
|
||||
"route": "/ACME/execution-workspaces/{executionWorkspaceId}/configuration",
|
||||
"captured_against": "master",
|
||||
"captured_sha": "058381349ec72d5bbffbb0c227d9fe576d3ce996",
|
||||
"depends_on": [
|
||||
"ui/src/pages/ExecutionWorkspaceDetail.tsx"
|
||||
]
|
||||
@@ -1483,9 +1472,9 @@
|
||||
"file": "dark/workspaces/issues.png",
|
||||
"theme": "dark",
|
||||
"viewport": "1440x900",
|
||||
"route": "/ACME/execution-workspaces/{workspaceId}/issues",
|
||||
"captured_against": "HEAD",
|
||||
"captured_sha": "911a1e8b0d24205acd5006e837a5c5e8c4bfb447",
|
||||
"route": "/ACME/execution-workspaces/{executionWorkspaceId}/issues",
|
||||
"captured_against": "master",
|
||||
"captured_sha": "058381349ec72d5bbffbb0c227d9fe576d3ce996",
|
||||
"depends_on": [
|
||||
"ui/src/pages/ExecutionWorkspaceDetail.tsx"
|
||||
]
|
||||
@@ -1495,8 +1484,8 @@
|
||||
"theme": "dark",
|
||||
"viewport": "1440x900",
|
||||
"route": "/ACME/workspaces",
|
||||
"captured_against": "HEAD",
|
||||
"captured_sha": "911a1e8b0d24205acd5006e837a5c5e8c4bfb447",
|
||||
"captured_against": "master",
|
||||
"captured_sha": "058381349ec72d5bbffbb0c227d9fe576d3ce996",
|
||||
"depends_on": [
|
||||
"ui/src/pages/Workspaces.tsx"
|
||||
]
|
||||
@@ -1505,9 +1494,20 @@
|
||||
"file": "dark/workspaces/runtime-logs.png",
|
||||
"theme": "dark",
|
||||
"viewport": "1440x900",
|
||||
"route": "/ACME/execution-workspaces/{workspaceId}/runtime-logs",
|
||||
"captured_against": "HEAD",
|
||||
"captured_sha": "911a1e8b0d24205acd5006e837a5c5e8c4bfb447",
|
||||
"route": "/ACME/execution-workspaces/{executionWorkspaceId}/runtime-logs",
|
||||
"captured_against": "master",
|
||||
"captured_sha": "058381349ec72d5bbffbb0c227d9fe576d3ce996",
|
||||
"depends_on": [
|
||||
"ui/src/pages/ExecutionWorkspaceDetail.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "dark/workspaces/services.png",
|
||||
"theme": "dark",
|
||||
"viewport": "1440x900",
|
||||
"route": "/ACME/execution-workspaces/{executionWorkspaceId}/services",
|
||||
"captured_against": "master",
|
||||
"captured_sha": "058381349ec72d5bbffbb0c227d9fe576d3ce996",
|
||||
"depends_on": [
|
||||
"ui/src/pages/ExecutionWorkspaceDetail.tsx"
|
||||
]
|
||||
@@ -2490,17 +2490,6 @@
|
||||
"ui/src/pages/OrgChart.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "light/org/execution-workspaces-list.png",
|
||||
"theme": "light",
|
||||
"viewport": "1440x900",
|
||||
"route": "/ACME/workspaces",
|
||||
"captured_against": "HEAD",
|
||||
"captured_sha": "911a1e8b0d24205acd5006e837a5c5e8c4bfb447",
|
||||
"depends_on": [
|
||||
"ui/src/pages/Workspaces.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "light/org/org-chart-add-agent.png",
|
||||
"theme": "light",
|
||||
@@ -2979,9 +2968,9 @@
|
||||
"file": "light/workspaces/configuration.png",
|
||||
"theme": "light",
|
||||
"viewport": "1440x900",
|
||||
"route": "/ACME/execution-workspaces/{workspaceId}/configuration",
|
||||
"captured_against": "HEAD",
|
||||
"captured_sha": "911a1e8b0d24205acd5006e837a5c5e8c4bfb447",
|
||||
"route": "/ACME/execution-workspaces/{executionWorkspaceId}/configuration",
|
||||
"captured_against": "master",
|
||||
"captured_sha": "058381349ec72d5bbffbb0c227d9fe576d3ce996",
|
||||
"depends_on": [
|
||||
"ui/src/pages/ExecutionWorkspaceDetail.tsx"
|
||||
]
|
||||
@@ -2990,9 +2979,9 @@
|
||||
"file": "light/workspaces/issues.png",
|
||||
"theme": "light",
|
||||
"viewport": "1440x900",
|
||||
"route": "/ACME/execution-workspaces/{workspaceId}/issues",
|
||||
"captured_against": "HEAD",
|
||||
"captured_sha": "911a1e8b0d24205acd5006e837a5c5e8c4bfb447",
|
||||
"route": "/ACME/execution-workspaces/{executionWorkspaceId}/issues",
|
||||
"captured_against": "master",
|
||||
"captured_sha": "058381349ec72d5bbffbb0c227d9fe576d3ce996",
|
||||
"depends_on": [
|
||||
"ui/src/pages/ExecutionWorkspaceDetail.tsx"
|
||||
]
|
||||
@@ -3002,8 +2991,8 @@
|
||||
"theme": "light",
|
||||
"viewport": "1440x900",
|
||||
"route": "/ACME/workspaces",
|
||||
"captured_against": "HEAD",
|
||||
"captured_sha": "911a1e8b0d24205acd5006e837a5c5e8c4bfb447",
|
||||
"captured_against": "master",
|
||||
"captured_sha": "058381349ec72d5bbffbb0c227d9fe576d3ce996",
|
||||
"depends_on": [
|
||||
"ui/src/pages/Workspaces.tsx"
|
||||
]
|
||||
@@ -3012,9 +3001,20 @@
|
||||
"file": "light/workspaces/runtime-logs.png",
|
||||
"theme": "light",
|
||||
"viewport": "1440x900",
|
||||
"route": "/ACME/execution-workspaces/{workspaceId}/runtime-logs",
|
||||
"captured_against": "HEAD",
|
||||
"captured_sha": "911a1e8b0d24205acd5006e837a5c5e8c4bfb447",
|
||||
"route": "/ACME/execution-workspaces/{executionWorkspaceId}/runtime-logs",
|
||||
"captured_against": "master",
|
||||
"captured_sha": "058381349ec72d5bbffbb0c227d9fe576d3ce996",
|
||||
"depends_on": [
|
||||
"ui/src/pages/ExecutionWorkspaceDetail.tsx"
|
||||
]
|
||||
},
|
||||
{
|
||||
"file": "light/workspaces/services.png",
|
||||
"theme": "light",
|
||||
"viewport": "1440x900",
|
||||
"route": "/ACME/execution-workspaces/{executionWorkspaceId}/services",
|
||||
"captured_against": "master",
|
||||
"captured_sha": "058381349ec72d5bbffbb0c227d9fe576d3ce996",
|
||||
"depends_on": [
|
||||
"ui/src/pages/ExecutionWorkspaceDetail.tsx"
|
||||
]
|
||||
|
||||
@@ -22,6 +22,16 @@ export const DEVICE_SCALE = 2;
|
||||
export const COMPANY_PREFIX = "ACME";
|
||||
export const COMPANY_NAME = "Acme Robotics";
|
||||
|
||||
/**
|
||||
* PAPERCLIP_INSTANCE_ID for the throw-away screenshot instance. Shared by
|
||||
* instanceEnv() (passed to the spawned server) and the DB-seeding helper, which
|
||||
* needs it to locate the embedded-postgres data dir under PAPERCLIP_HOME.
|
||||
*/
|
||||
export const INSTANCE_ID = "docs-screenshots";
|
||||
|
||||
/** Default embedded-postgres port (server/src/config.ts → embeddedPostgresPort). */
|
||||
export const EMBEDDED_POSTGRES_PORT = 54329;
|
||||
|
||||
// ── Paths ────────────────────────────────────────────────────────────────────
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
@@ -102,7 +112,7 @@ export function instanceEnv(home) {
|
||||
XDG_DATA_HOME: resolve(home, ".local", "share"),
|
||||
PORT: String(PORT),
|
||||
PAPERCLIP_HOME: home,
|
||||
PAPERCLIP_INSTANCE_ID: "docs-screenshots",
|
||||
PAPERCLIP_INSTANCE_ID: INSTANCE_ID,
|
||||
PAPERCLIP_BIND: "loopback",
|
||||
PAPERCLIP_DEPLOYMENT_MODE: "local_trusted",
|
||||
PAPERCLIP_DEPLOYMENT_EXPOSURE: "private",
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
* {goalId} → seed-ids.goalId
|
||||
* {routineId} → seed-ids.routineId
|
||||
* {issueId} → seed-ids.issueId
|
||||
* {workspaceId} → seed-ids.workspaceId
|
||||
* {workspaceId} → seed-ids.workspaceId (project primary workspace)
|
||||
* {executionWorkspaceId} → seed-ids.executionWorkspaceId (DB-seeded isolated ws)
|
||||
*/
|
||||
|
||||
import { COMPANY_PREFIX } from "./config.mjs";
|
||||
@@ -33,6 +34,7 @@ const ID_TOKENS = [
|
||||
"routineId",
|
||||
"issueId",
|
||||
"workspaceId",
|
||||
"executionWorkspaceId", // a real execution workspace (DB-seeded) for detail-page shots
|
||||
// Extended coverage tokens (written by seed.mjs; see scripts/screenshots/seed.mjs)
|
||||
"emptyPrefix", // a second, empty company's prefix — for empty-state shots
|
||||
"httpAgentId", // an agent created with the http adapter
|
||||
@@ -386,21 +388,30 @@ export const CAPTURE_TARGETS = [
|
||||
},
|
||||
|
||||
// ── Execution workspaces ───────────────────────────────────────────────────
|
||||
// These point at {executionWorkspaceId} (a real execution workspace seeded via
|
||||
// direct DB insert), NOT {workspaceId} (the project's primary workspace). The
|
||||
// detail page can only resolve an execution-workspace id.
|
||||
{
|
||||
name: "workspaces/configuration",
|
||||
route: "/{prefix}/execution-workspaces/{workspaceId}/configuration",
|
||||
route: "/{prefix}/execution-workspaces/{executionWorkspaceId}/configuration",
|
||||
dependsOn: ["ui/src/pages/ExecutionWorkspaceDetail.tsx"],
|
||||
themes: ["light", "dark"],
|
||||
},
|
||||
{
|
||||
name: "workspaces/services",
|
||||
route: "/{prefix}/execution-workspaces/{executionWorkspaceId}/services",
|
||||
dependsOn: ["ui/src/pages/ExecutionWorkspaceDetail.tsx"],
|
||||
themes: ["light", "dark"],
|
||||
},
|
||||
{
|
||||
name: "workspaces/runtime-logs",
|
||||
route: "/{prefix}/execution-workspaces/{workspaceId}/runtime-logs",
|
||||
route: "/{prefix}/execution-workspaces/{executionWorkspaceId}/runtime-logs",
|
||||
dependsOn: ["ui/src/pages/ExecutionWorkspaceDetail.tsx"],
|
||||
themes: ["light", "dark"],
|
||||
},
|
||||
{
|
||||
name: "workspaces/issues",
|
||||
route: "/{prefix}/execution-workspaces/{workspaceId}/issues",
|
||||
route: "/{prefix}/execution-workspaces/{executionWorkspaceId}/issues",
|
||||
dependsOn: ["ui/src/pages/ExecutionWorkspaceDetail.tsx"],
|
||||
themes: ["light", "dark"],
|
||||
},
|
||||
@@ -519,7 +530,8 @@ export const CAPTURE_TARGETS = [
|
||||
{ name: "org/org-chart-view", route: "/{prefix}/org", dependsOn: ["ui/src/pages/OrgChart.tsx"] },
|
||||
{ name: "org/org-chart-small-team", route: "/{prefix}/org", dependsOn: ["ui/src/pages/OrgChart.tsx"] },
|
||||
{ name: "org/skills-list", route: "/{prefix}/skills", dependsOn: ["ui/src/pages/CompanySkills.tsx"] },
|
||||
{ name: "org/execution-workspaces-list", route: "/{prefix}/workspaces", dependsOn: ["ui/src/pages/Workspaces.tsx"] },
|
||||
// Note: the workspaces list lives under workspaces/list (same /{prefix}/workspaces
|
||||
// route). The former org/execution-workspaces-list duplicate was removed.
|
||||
{
|
||||
name: "org/reassign",
|
||||
route: "/{prefix}/agents/{agentId}/configuration",
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
/**
|
||||
* seed-execution-workspace.mjs — provisions a realistic *execution workspace*
|
||||
* directly in the screenshot instance's embedded Postgres.
|
||||
*
|
||||
* Why direct DB access instead of REST: execution workspaces are never created
|
||||
* through a public API — they're born when an agent's heartbeat picks up a task
|
||||
* and the workspace-realization service carves out a git worktree. Reproducing a
|
||||
* full isolated agent run in the screenshot harness isn't practical, so for
|
||||
* screenshots we insert the rows the UI reads: one execution_workspaces row (with
|
||||
* runtime config in metadata.config.workspaceRuntime), a running runtime service,
|
||||
* a few workspace_operations log rows, and a couple of linked issues.
|
||||
*
|
||||
* Without this, the execution-workspace detail screenshots capture an endless
|
||||
* "Loading workspace…" state, because the detail page is pointed at a *project*
|
||||
* workspace id that the page can't resolve.
|
||||
*
|
||||
* The connection target is the embedded Postgres the onboard server starts under
|
||||
* PAPERCLIP_HOME. The `postgres` client is resolved from the parent repo's
|
||||
* node_modules (it isn't a dependency of paperclip-docs).
|
||||
*/
|
||||
|
||||
import { createRequire } from "node:module";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
import {
|
||||
EMBEDDED_POSTGRES_PORT,
|
||||
INSTANCE_ID,
|
||||
PARENT_REPO,
|
||||
scratchHome,
|
||||
} from "./config.mjs";
|
||||
|
||||
/** Resolve the `postgres` (postgres.js) client from the parent repo. */
|
||||
function loadPostgres() {
|
||||
// Base the require on a path inside the parent's db package so pnpm's
|
||||
// node_modules layout resolves `postgres` correctly.
|
||||
const requireFromParent = createRequire(resolve(PARENT_REPO, "packages/db/package.json"));
|
||||
return requireFromParent("postgres");
|
||||
}
|
||||
|
||||
/**
|
||||
* Work out which port the embedded Postgres is listening on. The onboard server
|
||||
* prefers `embeddedPostgresPort` (default 54329) but falls back to the next free
|
||||
* port if it's taken, so we read the live value from the cluster's
|
||||
* `postmaster.pid` (line 4 is the port), then the instance config.json, then the
|
||||
* compiled-in default.
|
||||
*/
|
||||
function resolvePostgresPort(home) {
|
||||
const instanceRoot = resolve(home, "instances", INSTANCE_ID);
|
||||
|
||||
try {
|
||||
const pidFile = resolve(instanceRoot, "db", "postmaster.pid");
|
||||
const lines = readFileSync(pidFile, "utf8").split("\n");
|
||||
const port = Number.parseInt(lines[3]?.trim() ?? "", 10);
|
||||
if (Number.isInteger(port) && port > 0) return port;
|
||||
} catch {
|
||||
/* fall through */
|
||||
}
|
||||
|
||||
try {
|
||||
const cfg = JSON.parse(readFileSync(resolve(instanceRoot, "config.json"), "utf8"));
|
||||
const port = cfg?.database?.embeddedPostgresPort;
|
||||
if (Number.isInteger(port) && port > 0) return port;
|
||||
} catch {
|
||||
/* fall through */
|
||||
}
|
||||
|
||||
return EMBEDDED_POSTGRES_PORT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert a demo execution workspace and its supporting rows.
|
||||
*
|
||||
* @param {object} input
|
||||
* @param {string} input.companyId
|
||||
* @param {string} input.projectId
|
||||
* @param {string|null} input.projectWorkspaceId the project's "main" workspace
|
||||
* @param {string|null} input.sourceIssueId issue that "provisioned" it
|
||||
* @param {string[]} input.linkedIssueIds issues to link (Tasks tab + list)
|
||||
* @param {string} input.issuePrefix e.g. "ACM" — used for branch name
|
||||
* @returns {Promise<{ executionWorkspaceId: string } | null>}
|
||||
*/
|
||||
export async function seedExecutionWorkspace(input) {
|
||||
const { companyId, projectId } = input;
|
||||
if (!companyId || !projectId) {
|
||||
console.warn("[seed-ws] missing companyId/projectId; skipping execution workspace seed");
|
||||
return null;
|
||||
}
|
||||
|
||||
const home = scratchHome();
|
||||
const port = resolvePostgresPort(home);
|
||||
const connectionString = `postgres://paperclip:[email protected]:${port}/paperclip`;
|
||||
|
||||
const postgres = loadPostgres();
|
||||
const sql = postgres(connectionString, { max: 1, idle_timeout: 5, onnotice: () => {} });
|
||||
|
||||
const prefix = input.issuePrefix ?? "ACM";
|
||||
const branchName = `${prefix}-204-workspace`;
|
||||
const cwd = `/srv/acme/website-worktrees/${prefix}-204`;
|
||||
const workspaceRuntime = {
|
||||
commands: [
|
||||
{
|
||||
id: "web",
|
||||
name: "web",
|
||||
kind: "service",
|
||||
command: "pnpm dev",
|
||||
cwd: ".",
|
||||
port: { type: "auto" },
|
||||
},
|
||||
{
|
||||
id: "db-migrate",
|
||||
name: "db:migrate",
|
||||
kind: "job",
|
||||
command: "pnpm db:migrate",
|
||||
cwd: ".",
|
||||
},
|
||||
],
|
||||
};
|
||||
const metadata = { config: { workspaceRuntime } };
|
||||
|
||||
try {
|
||||
// Reuse an existing demo workspace if a prior run already created one.
|
||||
const existing = await sql`
|
||||
select id from execution_workspaces
|
||||
where company_id = ${companyId} and branch_name = ${branchName}
|
||||
limit 1
|
||||
`;
|
||||
let executionWorkspaceId = existing[0]?.id ?? null;
|
||||
|
||||
if (!executionWorkspaceId) {
|
||||
const inserted = await sql`
|
||||
insert into execution_workspaces
|
||||
(company_id, project_id, project_workspace_id, source_issue_id,
|
||||
mode, strategy_type, name, status, cwd, repo_url, base_ref, branch_name,
|
||||
provider_type, metadata, last_used_at, opened_at)
|
||||
values
|
||||
(${companyId}, ${projectId}, ${input.projectWorkspaceId ?? null}, ${input.sourceIssueId ?? null},
|
||||
'isolated_workspace', 'git_worktree', ${"Hero section worktree"}, 'active',
|
||||
${cwd}, ${"https://github.com/acme-robotics/website"}, 'origin/main', ${branchName},
|
||||
'git_worktree', ${sql.json(metadata)}, now(), now() - interval '2 hours')
|
||||
returning id
|
||||
`;
|
||||
executionWorkspaceId = inserted[0].id;
|
||||
console.log(`[seed-ws] created execution workspace "${branchName}" (${executionWorkspaceId})`);
|
||||
} else {
|
||||
console.log(`[seed-ws] reusing execution workspace "${branchName}" (${executionWorkspaceId})`);
|
||||
await sql`delete from workspace_operations where execution_workspace_id = ${executionWorkspaceId}`;
|
||||
await sql`delete from workspace_runtime_services where execution_workspace_id = ${executionWorkspaceId}`;
|
||||
}
|
||||
|
||||
// A running web service so the Services tab + list show live status.
|
||||
await sql`
|
||||
insert into workspace_runtime_services
|
||||
(id, company_id, project_id, execution_workspace_id, scope_type, service_name,
|
||||
status, lifecycle, command, cwd, port, url, provider, health_status,
|
||||
last_used_at, started_at)
|
||||
values
|
||||
(gen_random_uuid(), ${companyId}, ${projectId}, ${executionWorkspaceId},
|
||||
'execution_workspace', 'web', 'running', 'shared', 'pnpm dev', ${cwd},
|
||||
5173, 'http://127.0.0.1:5173', 'local_process', 'healthy',
|
||||
now(), now() - interval '90 minutes')
|
||||
`;
|
||||
|
||||
// A handful of operation log rows for the Runtime logs tab — a successful
|
||||
// provision and service start, plus one failed job so the red stderr
|
||||
// excerpt is visible.
|
||||
const operations = [
|
||||
{
|
||||
phase: "workspace_provision",
|
||||
command: `git worktree add ../${prefix}-204 -b ${branchName} origin/main`,
|
||||
status: "succeeded",
|
||||
exitCode: 0,
|
||||
stdout: `Preparing worktree (new branch '${branchName}')\nHEAD is now at 9f2c1ab Merge pull request #482`,
|
||||
stderr: null,
|
||||
startOffset: "2 hours",
|
||||
endOffset: "118 minutes",
|
||||
},
|
||||
{
|
||||
phase: "workspace_provision",
|
||||
command: "pnpm install --frozen-lockfile",
|
||||
status: "succeeded",
|
||||
exitCode: 0,
|
||||
stdout: "Lockfile is up to date, resolution step is skipped\nPackages: +812\nDone in 9.4s",
|
||||
stderr: null,
|
||||
startOffset: "117 minutes",
|
||||
endOffset: "115 minutes",
|
||||
},
|
||||
{
|
||||
phase: "service_start",
|
||||
command: "pnpm dev",
|
||||
status: "running",
|
||||
exitCode: null,
|
||||
stdout: "VITE v5.4.2 ready in 412 ms\n➜ Local: http://127.0.0.1:5173/",
|
||||
stderr: null,
|
||||
startOffset: "90 minutes",
|
||||
endOffset: null,
|
||||
},
|
||||
{
|
||||
phase: "job_run",
|
||||
command: "pnpm db:migrate",
|
||||
status: "failed",
|
||||
exitCode: 1,
|
||||
stdout: null,
|
||||
stderr: "error: relation \"hero_variants\" already exists\n at Migrator.run (db/migrate.ts:54)",
|
||||
startOffset: "40 minutes",
|
||||
endOffset: "39 minutes",
|
||||
},
|
||||
];
|
||||
|
||||
for (const op of operations) {
|
||||
const finishedAt = op.endOffset ? sql`now() - interval ${sql.unsafe(`'${op.endOffset}'`)}` : null;
|
||||
await sql`
|
||||
insert into workspace_operations
|
||||
(company_id, execution_workspace_id, phase, command, cwd, status, exit_code,
|
||||
stdout_excerpt, stderr_excerpt, started_at, finished_at)
|
||||
values
|
||||
(${companyId}, ${executionWorkspaceId}, ${op.phase}, ${op.command}, ${cwd},
|
||||
${op.status}, ${op.exitCode}, ${op.stdout}, ${op.stderr},
|
||||
now() - interval ${sql.unsafe(`'${op.startOffset}'`)}, ${finishedAt})
|
||||
`;
|
||||
}
|
||||
console.log(`[seed-ws] inserted ${operations.length} workspace operations + 1 runtime service`);
|
||||
|
||||
// Link issues so the Tasks tab and the Workspaces list populate.
|
||||
const linkIds = (input.linkedIssueIds ?? []).filter(Boolean);
|
||||
if (linkIds.length > 0) {
|
||||
await sql`
|
||||
update issues set execution_workspace_id = ${executionWorkspaceId}
|
||||
where company_id = ${companyId} and id in ${sql(linkIds)}
|
||||
`;
|
||||
console.log(`[seed-ws] linked ${linkIds.length} issue(s) to the execution workspace`);
|
||||
}
|
||||
|
||||
return { executionWorkspaceId };
|
||||
} finally {
|
||||
await sql.end({ timeout: 5 });
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@
|
||||
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { BASE_URL, COMPANY_NAME, SEED_IDS_PATH } from "./config.mjs";
|
||||
import { seedExecutionWorkspace } from "./seed-execution-workspace.mjs";
|
||||
|
||||
// ── HTTP helpers ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -791,6 +792,32 @@ export default async function seed({ baseUrl = BASE_URL } = {}) {
|
||||
}
|
||||
});
|
||||
|
||||
// ── 14f. Execution workspace (direct DB insert) ─────────────────────────────
|
||||
// Execution workspaces have no public create endpoint — they're provisioned by
|
||||
// a real agent heartbeat. For screenshots we insert a realistic one directly so
|
||||
// the execution-workspace detail tabs render instead of "Loading workspace…".
|
||||
let executionWorkspaceId = null;
|
||||
if (projectId && issueId) {
|
||||
try {
|
||||
const linkedIssueIds = [
|
||||
issueId,
|
||||
issues["Fix flaky integration test"]?.id,
|
||||
issues["Add dark mode to the portal"]?.id,
|
||||
].filter(Boolean);
|
||||
const result = await seedExecutionWorkspace({
|
||||
companyId,
|
||||
projectId,
|
||||
projectWorkspaceId: workspaceId,
|
||||
sourceIssueId: issueId,
|
||||
linkedIssueIds,
|
||||
issuePrefix: companyPrefix,
|
||||
});
|
||||
executionWorkspaceId = result?.executionWorkspaceId ?? null;
|
||||
} catch (err) {
|
||||
console.warn("[seed] execution workspace seed failed (non-fatal):", err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// ── 15. Persist ids ─────────────────────────────────────────────────────────
|
||||
const ids = {
|
||||
companyPrefix,
|
||||
@@ -802,6 +829,7 @@ export default async function seed({ baseUrl = BASE_URL } = {}) {
|
||||
routineId,
|
||||
issueId,
|
||||
workspaceId,
|
||||
executionWorkspaceId,
|
||||
emptyPrefix,
|
||||
httpAgentId,
|
||||
skillId,
|
||||
|
||||