codeden 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/README.md +8 -2
  2. package/dist/THIRD-PARTY-NOTICES.txt +26 -0
  3. package/dist/cli.js +1 -1
  4. package/dist/drizzle/20261108085346_automation_created_by/migration.sql +1 -0
  5. package/dist/drizzle/20261108085346_automation_created_by/snapshot.json +3464 -0
  6. package/dist/drizzle/20261108090252_project_remote_warning/migration.sql +1 -0
  7. package/dist/drizzle/20261108090252_project_remote_warning/snapshot.json +3474 -0
  8. package/dist/{main-CzmG0d8t.js → main-B7VuaduN.js} +2 -2
  9. package/dist/{main-CzmG0d8t.js.map → main-B7VuaduN.js.map} +1 -1
  10. package/dist/{main-pXwL7GDQ.js → main-sEg3GS7X.js} +21687 -18876
  11. package/dist/main-sEg3GS7X.js.map +1 -0
  12. package/dist/{runner-CJetH9jW.js → runner-QPAnitMx.js} +844 -110
  13. package/dist/runner-QPAnitMx.js.map +1 -0
  14. package/dist/runner.js +1 -1
  15. package/dist/web/apple-touch-icon.png +0 -0
  16. package/dist/web/assets/{core-BeOBW-Wr.js → core-1ZGcgEnc.js} +1 -1
  17. package/dist/web/assets/engine-oniguruma-S10PniCp.js +1 -0
  18. package/dist/web/assets/{highlighted-body-JLMTFACH-D-L40tHI.js → highlighted-body-JLMTFACH-Gb9WrOV2.js} +1 -1
  19. package/dist/web/assets/{highlights-ZCqssNA3.js → highlights-BbG82Nvt.js} +1 -1
  20. package/dist/web/assets/index-CW3M7yYL.css +1 -0
  21. package/dist/web/assets/index-Capj1YIA.js +914 -0
  22. package/dist/web/assets/langs-92p-7kXy.js +1 -0
  23. package/dist/web/assets/mermaid-WFAXDIZX-D5w0oM_l.js +1 -0
  24. package/dist/web/assets/{shiki-CWXnlX2q.js → shiki-DvdkI_wn.js} +2 -2
  25. package/dist/web/assets/{stream-tokenizer-BpK1CZzW.js → stream-tokenizer-hzLEdzf1.js} +1 -1
  26. package/dist/web/assets/{themes-Bm7B3n10.js → themes-o5fWh38V.js} +2 -2
  27. package/dist/web/icons/icon-192.png +0 -0
  28. package/dist/web/icons/icon-512.png +0 -0
  29. package/dist/web/icons/maskable-192.png +0 -0
  30. package/dist/web/icons/maskable-512.png +0 -0
  31. package/dist/web/index.html +10 -2
  32. package/dist/web/manifest.webmanifest +28 -0
  33. package/package.json +1 -1
  34. package/dist/main-pXwL7GDQ.js.map +0 -1
  35. package/dist/runner-CJetH9jW.js.map +0 -1
  36. package/dist/web/assets/engine-oniguruma-BLpLufIT.js +0 -1
  37. package/dist/web/assets/index-D8qaPF_E.css +0 -1
  38. package/dist/web/assets/index-DR-I5EC5.js +0 -892
  39. package/dist/web/assets/langs-DKjK0GUN.js +0 -1
  40. package/dist/web/assets/mermaid-WFAXDIZX-fZTzmfWq.js +0 -1
@@ -8,7 +8,7 @@ import { createHash, randomBytes, randomUUID, timingSafeEqual } from "node:crypt
8
8
  import { execFile, spawn } from "node:child_process";
9
9
  import { gunzipSync } from "node:zlib";
10
10
  import { createInterface } from "node:readline";
11
- import { copyFile, mkdir, readFile, rm, rmdir, stat, utimes, writeFile } from "node:fs/promises";
11
+ import { chmod, copyFile, lstat, mkdir, readFile, readdir, realpath, rename, rm, rmdir, stat, unlink, utimes, writeFile } from "node:fs/promises";
12
12
  import { STATUS_CODES, ServerResponse, createServer } from "node:http";
13
13
  import { Http2ServerRequest, constants as constants$1 } from "node:http2";
14
14
  import { Readable } from "node:stream";
@@ -6004,12 +6004,16 @@ const Role = _enum([
6004
6004
  "agent"
6005
6005
  ]);
6006
6006
  /**
6007
- * Autonomy levels (§11.2), mapped onto each provider's native enforcement.
6007
+ * Permission levels of an agent session (§11.2), mapped onto each provider's native enforcement.
6008
+ * Shown in the UI as "Permissions"; stored as `autonomy`.
6009
+ * - `read_only`: reads and runs checks, but never edits files, commits or pushes. Needs a
6010
+ * provider with `ProviderCapabilities.readOnly`.
6008
6011
  * - `supervised`: every edit and command asks.
6009
6012
  * - `auto`: edits and normal commands in the worktree run freely; risky actions ask (default).
6010
6013
  * - `full`: no approvals inside the worktree.
6011
6014
  */
6012
6015
  const AutonomyLevel = _enum([
6016
+ "read_only",
6013
6017
  "supervised",
6014
6018
  "auto",
6015
6019
  "full"
@@ -6284,11 +6288,47 @@ const Attachment = discriminatedUnion("type", [object({
6284
6288
  name: string().optional(),
6285
6289
  mediaType: string().optional()
6286
6290
  })]);
6291
+ /**
6292
+ * Who sent a message to a session: the user, the orchestrator (to a PM), a PM (to its
6293
+ * sessions), an automation, or CodeDen itself (automatic wake-ups, the factory loop).
6294
+ */
6295
+ const MessageSender = _enum([
6296
+ "user",
6297
+ "orchestrator",
6298
+ "pm",
6299
+ "automation",
6300
+ "codeden"
6301
+ ]);
6287
6302
  /** Input that starts or steers a turn: text plus optional image/file attachments. */
6288
6303
  const UserInput = object({
6289
6304
  text: string(),
6290
- attachments: array(Attachment).default([])
6305
+ attachments: array(Attachment).default([]),
6306
+ /**
6307
+ * Who wrote the message, when it was not the user: recorded so the transcript can show the
6308
+ * sender without reading the text. Absent on the user's own messages and on every message
6309
+ * stored before senders were recorded.
6310
+ */
6311
+ from: MessageSender.optional()
6291
6312
  });
6313
+ /**
6314
+ * The first line of a message from a PM or the orchestrator, telling the agent who wrote it.
6315
+ * The only definition of these words: the server writes them, the UI recognises them.
6316
+ */
6317
+ const SENDER_LINES = {
6318
+ pm: "Message from your project manager:",
6319
+ orchestrator: "Message from the orchestrator:"
6320
+ };
6321
+ /**
6322
+ * Input for a message from a PM or the orchestrator: the sender is recorded in `from` and
6323
+ * named for the agent in the text's first line (`SENDER_LINES`).
6324
+ */
6325
+ function messageFrom(from, text) {
6326
+ return {
6327
+ text: `${SENDER_LINES[from]}\n${text}`,
6328
+ attachments: [],
6329
+ from
6330
+ };
6331
+ }
6292
6332
  const itemBase = {
6293
6333
  /** Unique within the session. */
6294
6334
  id: Id,
@@ -6813,6 +6853,18 @@ const FactoryLoopState = _enum([
6813
6853
  "stopped"
6814
6854
  ]);
6815
6855
  // ../../packages/contracts/src/entities/project.ts
6856
+ /**
6857
+ * Something re-detecting a project's folder found that CodeDen does not apply on its own (§9).
6858
+ * `github_remote_missing`: a `github` project's folder no longer has a GitHub `origin` (removed,
6859
+ * pointing at another host, or no git checkout at all). The project keeps its GitHub
6860
+ * coordinates until the user restores the remote or disconnects GitHub.
6861
+ */
6862
+ const ProjectRemoteWarning = object({
6863
+ code: literal("github_remote_missing"),
6864
+ /** What `origin` points at now, credentials removed; null when there is no `origin`. */
6865
+ remoteUrl: string().nullable(),
6866
+ detectedAt: Timestamp
6867
+ });
6816
6868
  const projectBase = {
6817
6869
  id: Id,
6818
6870
  name: string().min(1),
@@ -6845,6 +6897,12 @@ const projectBase = {
6845
6897
  * `projects.reordered`; archived projects keep theirs.
6846
6898
  */
6847
6899
  sortOrder: int().nonnegative().default(0),
6900
+ /**
6901
+ * Set when re-detection found the folder's git remote out of step with the project (see
6902
+ * `ProjectRemoteWarning`); absent when all is well. Cleared by the next detection that finds
6903
+ * the GitHub remote again, or by disconnecting GitHub.
6904
+ */
6905
+ remoteWarning: ProjectRemoteWarning.optional(),
6848
6906
  createdAt: Timestamp,
6849
6907
  updatedAt: Timestamp,
6850
6908
  archivedAt: Timestamp.nullable()
@@ -6859,6 +6917,9 @@ const GitHubRepo = object({
6859
6917
  /**
6860
6918
  * A project. `github` projects get the full issue/PR/evidence flow; `local` projects
6861
6919
  * (no GitHub remote) get sessions, worktrees, checkpoints, diffs and automations only (§9).
6920
+ * The kind is detected when the project is added and again later (on server start, after
6921
+ * agent turns in local projects, on demand): a folder that gains a GitHub `origin` becomes a
6922
+ * `github` project. Losing the remote never downgrades a project on its own.
6862
6923
  */
6863
6924
  const Project = discriminatedUnion("kind", [object({
6864
6925
  ...projectBase,
@@ -7044,7 +7105,13 @@ const ProviderCapabilities = object({
7044
7105
  * An autonomy change reaches a running turn from its next tool call. When false (or absent),
7045
7106
  * a running turn keeps its autonomy and the change applies from the next turn.
7046
7107
  */
7047
- liveAutonomy: boolean().optional()
7108
+ liveAutonomy: boolean().optional(),
7109
+ /**
7110
+ * The `read_only` permission level is enforced natively: no file edits, no writing shell
7111
+ * commands, no commits or pushes (§11.2). When false (or absent), sessions on this provider
7112
+ * cannot use `read_only`.
7113
+ */
7114
+ readOnly: boolean().optional()
7048
7115
  });
7049
7116
  /** A model a provider offers. */
7050
7117
  const ModelInfo = object({
@@ -7703,7 +7770,11 @@ const RunnerCommandResults = {
7703
7770
  ok: boolean()
7704
7771
  })).optional()
7705
7772
  }),
7706
- "workspace.remove": object({ removed: boolean() }),
7773
+ "workspace.remove": object({
7774
+ removed: boolean(),
7775
+ /** A forced removal deleted uncommitted changes (absent from older runners). */
7776
+ discardedChanges: boolean().optional()
7777
+ }),
7707
7778
  "session.start": object({ resumeToken: string().nullable() }),
7708
7779
  "session.send": Empty,
7709
7780
  "session.steer": Empty,
@@ -7813,13 +7884,7 @@ const QueuedMessage = object({
7813
7884
  sessionId: Id,
7814
7885
  input: UserInput,
7815
7886
  /** Who queued it. Events written before senders were recorded were all the user's. */
7816
- from: _enum([
7817
- "user",
7818
- "orchestrator",
7819
- "pm",
7820
- "automation",
7821
- "codeden"
7822
- ]).default("user"),
7887
+ from: MessageSender.default("user"),
7823
7888
  createdAt: Timestamp
7824
7889
  });
7825
7890
  object({ messages: array(QueuedMessage) });
@@ -8171,6 +8236,12 @@ const AutomationGuards = object({
8171
8236
  /** Run once for missed schedules after downtime. */
8172
8237
  catchUpMissed: boolean().default(false)
8173
8238
  });
8239
+ /** Who created a database automation (§10). */
8240
+ const AutomationCreator = _enum([
8241
+ "user",
8242
+ "pm",
8243
+ "import"
8244
+ ]);
8174
8245
  /** An automation: trigger + action + target + guards. Off by default. */
8175
8246
  const Automation = object({
8176
8247
  id: Id,
@@ -8196,6 +8267,11 @@ const Automation = object({
8196
8267
  * `automation:<template id or name slug>`.
8197
8268
  */
8198
8269
  workKey: string().min(1).max(200).optional(),
8270
+ /**
8271
+ * Who created a database automation: the user in the UI (`user`, the default when absent),
8272
+ * a PM through `create_automation` (`pm`), or an import of shared files (`import`).
8273
+ */
8274
+ createdBy: AutomationCreator.optional(),
8199
8275
  createdAt: Timestamp,
8200
8276
  updatedAt: Timestamp
8201
8277
  });
@@ -9625,7 +9701,7 @@ const Settings = object({
9625
9701
  /** Stops every project's factory loop and all factory sessions. */
9626
9702
  killSwitch: boolean().default(false) }).prefault({}),
9627
9703
  /**
9628
- * Worktree cleanup (§9). Archiving a session always removes its worktree; with `cleanup` on,
9704
+ * Worktree cleanup (§9). Archiving a session removes its worktree (unless it has uncommitted changes and isn't forced); with `cleanup` on,
9629
9705
  * worktrees of sessions inactive for `afterDays` are removed too, unless the session's pull
9630
9706
  * request is still open or the worktree has uncommitted changes.
9631
9707
  */
@@ -9928,20 +10004,22 @@ const CreateProjectRequest = object({
9928
10004
  defaultProvider: ProviderId.nullable().optional(),
9929
10005
  autonomy: AutonomyLevel.nullable().optional()
9930
10006
  });
10007
+ /** What CodeDen found in a project folder. */
10008
+ const ProjectDetection = object({
10009
+ isGitRepo: boolean(),
10010
+ /** Top level of the git checkout (may differ from the chosen folder). */
10011
+ gitRoot: string().nullable(),
10012
+ remoteUrl: string().nullable(),
10013
+ github: GitHubRepo.nullable(),
10014
+ defaultBranch: string().nullable(),
10015
+ currentBranch: string().nullable(),
10016
+ packageManager: PackageManager.nullable(),
10017
+ /** Suggested worktree setup commands, e.g. `pnpm install`. */
10018
+ suggestedSetupCommands: array(string())
10019
+ });
9931
10020
  object({
9932
10021
  project: Project,
9933
- detected: object({
9934
- isGitRepo: boolean(),
9935
- /** Top level of the git checkout (may differ from the chosen folder). */
9936
- gitRoot: string().nullable(),
9937
- remoteUrl: string().nullable(),
9938
- github: GitHubRepo.nullable(),
9939
- defaultBranch: string().nullable(),
9940
- currentBranch: string().nullable(),
9941
- packageManager: PackageManager.nullable(),
9942
- /** Suggested worktree setup commands, e.g. `pnpm install`. */
9943
- suggestedSetupCommands: array(string())
9944
- })
10022
+ detected: ProjectDetection
9945
10023
  });
9946
10024
  /** `PATCH /api/projects/:id`. */
9947
10025
  const UpdateProjectRequest = object({
@@ -10540,6 +10618,97 @@ object({
10540
10618
  /** The session start when accepted (possibly `conflict`); null when declined. */
10541
10619
  start: StartSessionResponse.nullable()
10542
10620
  });
10621
+ // ../../packages/contracts/src/automations-sharing.ts
10622
+ /**
10623
+ * Sharing automations (§10): duplicate, export as the Markdown + YAML frontmatter file format
10624
+ * (one `.md`, or all of a project's as a `.zip`), and import those files back. Imports are
10625
+ * parsed with the same parser and validation as automation files, never run anything and
10626
+ * always start off.
10627
+ */
10628
+ /** Size limits for an import, checked by the server before anything is parsed. */
10629
+ const AUTOMATION_IMPORT_LIMITS = {
10630
+ /** Automation files in one import (Markdown files, zip entries or pasted text). */
10631
+ maxFiles: 50,
10632
+ /** One file's text, in UTF-16 code units (about bytes for ASCII). */
10633
+ maxFileChars: 131072,
10634
+ /** All files together, after unzipping. */
10635
+ maxTotalChars: 1048576,
10636
+ /** A zip as uploaded, before base64 (base64 makes it about a third bigger). */
10637
+ maxZipBytes: 1048576,
10638
+ /** Entries a zip may list, `.md` or not (more is refused before inflating anything). */
10639
+ maxZipEntries: 200
10640
+ };
10641
+ /** What to import: Markdown files, a zip of them (base64) and/or pasted Markdown. */
10642
+ const AutomationImportSource = object({
10643
+ files: array(object({
10644
+ /** File name, e.g. `release-notes.md`; its stem becomes the default work key. */
10645
+ name: string().trim().min(1).max(255),
10646
+ content: string().max(AUTOMATION_IMPORT_LIMITS.maxFileChars)
10647
+ })).max(AUTOMATION_IMPORT_LIMITS.maxFiles).optional(),
10648
+ /** A `.zip` of `.md` files, base64-encoded. Other entries are ignored. */
10649
+ zipBase64: string().max(Math.ceil(AUTOMATION_IMPORT_LIMITS.maxZipBytes / 3) * 4 + 4).optional(),
10650
+ /** One automation pasted as text. */
10651
+ text: string().max(AUTOMATION_IMPORT_LIMITS.maxFileChars).optional()
10652
+ }).refine((s) => Boolean(s.files?.length || s.zipBase64 || s.text?.trim()), { message: "Pass files, a zip or pasted text" });
10653
+ /** The definition an import would create (no id, target or switch yet). */
10654
+ const AutomationDraft = object({
10655
+ name: string().min(1),
10656
+ description: string().nullable(),
10657
+ trigger: AutomationTrigger,
10658
+ action: AutomationAction,
10659
+ guards: AutomationGuards,
10660
+ workKey: string().min(1).max(200)
10661
+ });
10662
+ object({ items: array(object({
10663
+ /** File name, unique within the import (duplicates get ` (2)`). */
10664
+ fileName: string(),
10665
+ /** Null when the file is invalid. */
10666
+ automation: AutomationDraft.nullable(),
10667
+ /** Validation error (bad YAML, unknown fields, invalid cron, ...); null when importable. */
10668
+ error: string().nullable(),
10669
+ /** Runs with Full permissions: it never asks before acting. */
10670
+ fullPermissions: boolean()
10671
+ })) });
10672
+ /**
10673
+ * `POST /api/automations/import`: re-parse the same source and create the selected files as
10674
+ * database automations, always off. `projectId` targets one project; omitted, all projects.
10675
+ */
10676
+ const ImportAutomationsRequest = object({
10677
+ source: AutomationImportSource,
10678
+ projectId: Id.optional(),
10679
+ /** File names (from the preview) to import; omitted, every valid file. */
10680
+ select: array(string()).max(AUTOMATION_IMPORT_LIMITS.maxFiles).optional()
10681
+ });
10682
+ object({
10683
+ /** The new automations, all off. */
10684
+ created: array(AutomationListItem),
10685
+ /** Selected files that could not be imported, with the reason. */
10686
+ failed: array(object({
10687
+ fileName: string(),
10688
+ error: string()
10689
+ }))
10690
+ });
10691
+ object({
10692
+ /** `<slug>.md`. */
10693
+ fileName: string().min(1),
10694
+ markdown: string()
10695
+ });
10696
+ /** `GET /api/automations/export?projectId=`: the query; without a project, every automation. */
10697
+ const ExportAutomationsQuery = object({ projectId: Id.optional() });
10698
+ object({
10699
+ fileName: string().min(1),
10700
+ /** The zip, base64-encoded. */
10701
+ zipBase64: string(),
10702
+ count: int().nonnegative()
10703
+ });
10704
+ /**
10705
+ * `POST /api/automations/:id/duplicate`: an editable copy (also of file-based and built-in
10706
+ * automations), off. `projectId` targets one project; omitted, the original's target.
10707
+ */
10708
+ const DuplicateAutomationRequest = object({
10709
+ projectId: Id.optional(),
10710
+ name: string().trim().min(1).max(200).optional()
10711
+ });
10543
10712
  // ../../packages/contracts/src/browser-api.ts
10544
10713
  /**
10545
10714
  * Browser panel HTTP and WebSocket shapes (§12.2). Routes live under
@@ -10841,6 +11010,201 @@ object({
10841
11010
  message: string()
10842
11011
  }).optional()
10843
11012
  });
11013
+ /** The password rules in plain words, shown next to the password field. */
11014
+ const PASSWORD_RULE_TEXT = "Use at least 15 characters. A short sentence or a few random words works well. Very common passwords, repeated characters and simple sequences such as abcdef… or 12345… are refused.";
11015
+ /** Base words of very common passwords; a password made of one of these plus digits is refused. */
11016
+ const COMMON_WORDS = /* @__PURE__ */ new Set([
11017
+ "password",
11018
+ "passw0rd",
11019
+ "letmein",
11020
+ "welcome",
11021
+ "admin",
11022
+ "administrator",
11023
+ "iloveyou",
11024
+ "qwerty",
11025
+ "monkey",
11026
+ "dragon",
11027
+ "football",
11028
+ "baseball",
11029
+ "sunshine",
11030
+ "princess",
11031
+ "superman",
11032
+ "batman",
11033
+ "trustno",
11034
+ "master",
11035
+ "shadow",
11036
+ "michael",
11037
+ "jennifer",
11038
+ "computer",
11039
+ "secret",
11040
+ "changeme",
11041
+ "codeden",
11042
+ "default",
11043
+ "login",
11044
+ "hello",
11045
+ "abc",
11046
+ "test",
11047
+ "guest",
11048
+ "root",
11049
+ "starwars",
11050
+ "whatever",
11051
+ "freedom",
11052
+ "mustang",
11053
+ "pokemon",
11054
+ "charlie",
11055
+ "correcthorsebatterystaple"
11056
+ ]);
11057
+ /** Character runs a "sequence" password walks along (alphabet, digits, keyboard rows). */
11058
+ const SEQUENCES = [
11059
+ "abcdefghijklmnopqrstuvwxyz",
11060
+ "0123456789",
11061
+ "qwertyuiopasdfghjklzxcvbnm",
11062
+ "qwertzuiopasdfghjklyxcvbnm",
11063
+ "azertyuiopqsdfghjklmwxcvbn",
11064
+ "1qaz2wsx3edc4rfv5tgb6yhn7ujm8ik9ol0p",
11065
+ "qazwsxedcrfvtgbyhnujmikolp",
11066
+ "1234567890qwertyuiopasdfghjklzxcvbnm"
11067
+ ];
11068
+ /** True when `text` is one short unit repeated (`abcabcabc`, `passwordpassword`). */
11069
+ function isRepeatedUnit(text) {
11070
+ for (let size = 1; size <= text.length / 2; size++) {
11071
+ if (text.length % size !== 0) continue;
11072
+ if (text.slice(0, size).repeat(text.length / size) === text) return true;
11073
+ }
11074
+ return false;
11075
+ }
11076
+ /** True when `text` walks along one of the known sequences, forwards or backwards (wrapping). */
11077
+ function isSequence(text) {
11078
+ return SEQUENCES.some((seq) => {
11079
+ const looped = seq.repeat(Math.ceil(text.length / seq.length) + 2);
11080
+ const reversed = [...looped].reverse().join("");
11081
+ return looped.includes(text) || reversed.includes(text);
11082
+ });
11083
+ }
11084
+ /**
11085
+ * Why `password` can't be used for network access, in plain words, or null when it is fine.
11086
+ * Shared by the server (which enforces it) and the UI (which explains it as you type).
11087
+ */
11088
+ function passwordProblem(password) {
11089
+ if (password.length < 15) return `Use at least 15 characters (this one has ${password.length}).`;
11090
+ if (password.length > 256) return `Use at most 256 characters.`;
11091
+ const lower = password.toLowerCase();
11092
+ const compact = lower.replace(/\s+/g, "");
11093
+ if (new Set(compact).size < 5 || isRepeatedUnit(compact)) return "That repeats the same few characters. Try a few random words instead.";
11094
+ if (isSequence(compact)) return "That is a simple sequence. Try a few random words instead.";
11095
+ const letters = lower.replace(/[^a-z]/g, "");
11096
+ const words = lower.split(/[^a-z]+/).filter(Boolean);
11097
+ if (letters.length > 0 && (COMMON_WORDS.has(letters) || isRepeatedUnitOf(letters, COMMON_WORDS) || words.length > 0 && words.every((w) => COMMON_WORDS.has(w)))) return "That is too close to a very common password. Try a few random words instead.";
11098
+ return null;
11099
+ }
11100
+ function isRepeatedUnitOf(text, units) {
11101
+ for (const unit of units) if (text.length % unit.length === 0 && unit.repeat(text.length / unit.length) === text) return true;
11102
+ return false;
11103
+ }
11104
+ /** One address CodeDen can be opened on, with the URL a phone would scan. */
11105
+ const NetworkAddress = object({
11106
+ kind: _enum([
11107
+ "local",
11108
+ "lan",
11109
+ "vpn"
11110
+ ]),
11111
+ /** Human label, e.g. "This computer", "Wi-Fi (192.168.1.20)", "Tailscale". */
11112
+ label: string(),
11113
+ /** The interface the address belongs to (`Wi-Fi`, `eth0`, `tailscale0`), or null for localhost. */
11114
+ interfaceName: string().nullable(),
11115
+ /** The bare host: `localhost` or an IPv4 address. */
11116
+ host: string(),
11117
+ /** `http://<host>:<port>`. */
11118
+ url: string()
11119
+ });
11120
+ /** A signed-in remote device, as listed in Settings › Network access (never the token). */
11121
+ const RemoteDevice = object({
11122
+ id: string(),
11123
+ /** The browser's user agent when it signed in (trimmed). */
11124
+ label: string(),
11125
+ /** The address it signed in from. */
11126
+ address: string().nullable(),
11127
+ createdAt: Timestamp,
11128
+ lastSeenAt: Timestamp,
11129
+ expiresAt: Timestamp,
11130
+ /** The device making this request. */
11131
+ current: boolean()
11132
+ });
11133
+ /**
11134
+ * `GET /api/network`: whether network access is on, where CodeDen answers, and who is signed
11135
+ * in. `remote` tells the caller whether it is itself a remote device (management is
11136
+ * loopback-only, so the UI shows the controls read-only there).
11137
+ */
11138
+ const NetworkStatus = object({
11139
+ enabled: boolean(),
11140
+ passwordSet: boolean(),
11141
+ passwordSetAt: Timestamp.nullable(),
11142
+ /** Where the HTTP listener is bound right now. */
11143
+ listening: _enum(["loopback", "network"]),
11144
+ port: int().nonnegative(),
11145
+ /** Addresses CodeDen answers on: localhost always, LAN and VPN addresses when enabled. */
11146
+ addresses: array(NetworkAddress),
11147
+ /** Addresses it would answer on once enabled (for the off state's preview). */
11148
+ candidates: array(NetworkAddress),
11149
+ devices: array(RemoteDevice),
11150
+ remote: boolean()
11151
+ });
11152
+ /**
11153
+ * `POST /api/network/password` (loopback only): set a password, or let the server generate a
11154
+ * passphrase. Either way every remote device is signed out.
11155
+ */
11156
+ const SetNetworkPasswordRequest = union([object({ password: string().min(1).max(256) }), object({ generate: literal(true) })]);
11157
+ /** Response to setting the password: the generated passphrase is returned once, never again. */
11158
+ const SetNetworkPasswordResponse = object({
11159
+ status: NetworkStatus,
11160
+ generated: string().nullable()
11161
+ });
11162
+ /** `PUT /api/network` (loopback only): turn network access on or off. */
11163
+ const SetNetworkAccessRequest = object({ enabled: boolean() });
11164
+ /** `POST /api/auth/sign-in` from a remote device. */
11165
+ const SignInRequest = object({ password: string().max(256) });
11166
+ object({
11167
+ /** The caller is not on this machine. */
11168
+ remote: boolean(),
11169
+ /** The caller has a valid session (remote) or install-token cookie (local). */
11170
+ signedIn: boolean(),
11171
+ networkEnabled: boolean()
11172
+ });
11173
+ object({ error: object({
11174
+ code: _enum([
11175
+ "invalid_password",
11176
+ "rate_limited",
11177
+ "locked_out",
11178
+ "network_off"
11179
+ ]),
11180
+ message: string(),
11181
+ retryAfterSec: int().nonnegative().nullable()
11182
+ }) });
11183
+ /** A stored remote session: only the SHA-256 hash of its token is kept. */
11184
+ const RemoteSessionRecord = object({
11185
+ id: string(),
11186
+ tokenHash: string().regex(/^[0-9a-f]{64}$/),
11187
+ label: string(),
11188
+ address: string().nullable(),
11189
+ createdAt: Timestamp,
11190
+ lastSeenAt: Timestamp,
11191
+ expiresAt: Timestamp
11192
+ });
11193
+ /**
11194
+ * `<home>/remote-access.json` (mode 0600): the network switch, the password hash (scrypt,
11195
+ * salted; never the password) and the remote sessions. Kept out of `settings.json` so the
11196
+ * settings API can't switch network access on without the checks.
11197
+ */
11198
+ const RemoteAccessFile = object({
11199
+ schemaVersion: literal(1).default(1),
11200
+ enabled: boolean().default(false),
11201
+ password: object({
11202
+ /** `scrypt$<N>$<r>$<p>$<salt b64>$<hash b64>`. */
11203
+ hash: string().startsWith("scrypt$"),
11204
+ setAt: Timestamp
11205
+ }).nullable().default(null),
11206
+ sessions: array(RemoteSessionRecord).default([])
11207
+ });
10844
11208
  object({ session: Session.nullable() });
10845
11209
  /** `POST .../orchestrator/messages` and `.../projects/:id/pm/messages`. */
10846
11210
  const RoleMessageRequest = object({
@@ -10911,6 +11275,25 @@ object({
10911
11275
  message: string().nullable()
10912
11276
  })
10913
11277
  });
11278
+ object({
11279
+ /** The project after the check (unchanged unless `changed`). */
11280
+ project: Project,
11281
+ outcome: _enum([
11282
+ "unchanged",
11283
+ "git_detected",
11284
+ "github_connected",
11285
+ "github_changed",
11286
+ "github_remote_missing",
11287
+ "folder_missing"
11288
+ ]),
11289
+ /** True when the check updated the project (a `project.updated` event was appended). */
11290
+ changed: boolean(),
11291
+ /** One line for the UI, e.g. "Connected to octo/app" or "Still no GitHub remote on origin". */
11292
+ message: string(),
11293
+ /** What was found in the folder; null when the folder is missing. */
11294
+ detected: ProjectDetection.nullable()
11295
+ });
11296
+ object({ project: Project });
10914
11297
  // ../../packages/contracts/src/runner/events.ts
10915
11298
  /**
10916
11299
  * Durable runner -> server events. Journaled by the runner and delivered with a `seq`
@@ -11439,7 +11822,7 @@ object({
11439
11822
  * rest of the bundle, so the package version is the single source of truth at runtime;
11440
11823
  * everywhere else (workspace, tests) it is `SOURCE_VERSION`.
11441
11824
  */
11442
- const CODEDEN_VERSION = packageVersion() ?? "0.2.0";
11825
+ const CODEDEN_VERSION = packageVersion() ?? "0.4.0";
11443
11826
  function packageVersion() {
11444
11827
  const value = globalThis.__CODEDEN_PACKAGE_VERSION__;
11445
11828
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -15593,9 +15976,11 @@ function explicitModel(model) {
15593
15976
  }
15594
15977
  // ../../packages/providers/src/policy/read-only.ts
15595
15978
  /**
15596
- * Read-only sessions (the orchestrator and PMs, §11.2) may inspect a repository but never
15597
- * change it. This allowlist decides which shell commands they may run: commands that only
15598
- * read, with no output redirection and no command substitution. Anything else is denied.
15979
+ * Read-only sessions (the orchestrator and PMs, and agent sessions with the `read_only`
15980
+ * permission level, §11.2) may inspect a repository but never change it. This allowlist decides
15981
+ * which shell commands they may run: commands that only read, with no output redirection and no
15982
+ * command substitution. Agent sessions may also run the project's checks (tests, lint, type
15983
+ * checks) without fix or update flags. Anything else is denied.
15599
15984
  */
15600
15985
  /**
15601
15986
  * Split on every `;`, `&&`, `||`, `|` and newline, even inside quotes. Deliberately cruder
@@ -15705,8 +16090,75 @@ const READ_ONLY_GH = /* @__PURE__ */ new Set([
15705
16090
  "checks"
15706
16091
  ]);
15707
16092
  const bin = (token) => (token ?? "").replace(/\\/g, "/").split("/").pop()?.toLowerCase().replace(/\.(exe|cmd|bat|ps1)$/, "") ?? "";
16093
+ /** Package managers whose `test`, `lint`, ... scripts count as checks. */
16094
+ const PACKAGE_MANAGERS = /* @__PURE__ */ new Set([
16095
+ "npm",
16096
+ "pnpm",
16097
+ "yarn",
16098
+ "bun"
16099
+ ]);
16100
+ /** Script names a package manager may run as a check (`pnpm test`, `npm run lint`). */
16101
+ const CHECK_SCRIPTS = /* @__PURE__ */ new Set([
16102
+ "test",
16103
+ "tests",
16104
+ "lint",
16105
+ "typecheck",
16106
+ "type-check",
16107
+ "check",
16108
+ "types",
16109
+ "vitest",
16110
+ "jest",
16111
+ "biome",
16112
+ "eslint",
16113
+ "tsc"
16114
+ ]);
16115
+ /** Programs (null: any arguments) and `<program> <subcommand>` pairs that run checks. */
16116
+ const CHECK_COMMANDS = {
16117
+ tsc: null,
16118
+ vitest: null,
16119
+ jest: null,
16120
+ eslint: null,
16121
+ pytest: null,
16122
+ mypy: null,
16123
+ pyright: null,
16124
+ biome: [
16125
+ "check",
16126
+ "lint",
16127
+ "ci"
16128
+ ],
16129
+ ruff: ["check"],
16130
+ cargo: [
16131
+ "test",
16132
+ "check",
16133
+ "clippy"
16134
+ ],
16135
+ go: ["test", "vet"],
16136
+ dotnet: ["test"],
16137
+ mvn: ["test", "verify"],
16138
+ gradle: ["test", "check"]
16139
+ };
16140
+ /** Flags that turn a check into a write (fixing, formatting, updating snapshots, watching). */
16141
+ const WRITING_FLAG = /^(--fix|--fix-type=.*|--write|--apply|--apply-unsafe|--unsafe|-u|--update|--update-snapshots?|--updatesnapshot|--watch|-w)(=.*)?$/;
16142
+ /** True when `name args` runs a known check without writing flags. */
16143
+ function isCheck(name, args) {
16144
+ if (args.some((a) => WRITING_FLAG.test(a))) return false;
16145
+ const positional = args.filter((a) => !a.startsWith("-"));
16146
+ if (PACKAGE_MANAGERS.has(name)) {
16147
+ const script = positional[0] === "run" ? positional[1] : positional[0];
16148
+ return script !== void 0 && CHECK_SCRIPTS.has(script);
16149
+ }
16150
+ if (name === "npx" || name === "pnpx" || name === "bunx") {
16151
+ const tool = positional[0];
16152
+ return tool !== void 0 && isCheck(bin(tool), args.slice(args.indexOf(tool) + 1));
16153
+ }
16154
+ if (name === "python" || name === "python3" || name === "py") return args[0] === "-m" && (args[1] === "pytest" || args[1] === "mypy");
16155
+ if (!Object.hasOwn(CHECK_COMMANDS, name)) return false;
16156
+ const subs = CHECK_COMMANDS[name];
16157
+ if (!subs) return true;
16158
+ return positional[0] !== void 0 && subs.includes(positional[0]);
16159
+ }
15708
16160
  /** Why a command is refused in a read-only session, or undefined when it may run. */
15709
- function readOnlyCommandDenial(command) {
16161
+ function readOnlyCommandDenial(command, options = {}) {
15710
16162
  if (/\$\(|`|<\(|>\(/.test(command)) return "Command substitution is not allowed in a read-only session";
15711
16163
  for (const simple of splitCommands(command)) {
15712
16164
  if (/(^|[^0-9&<])>>?(?!\s*(&\d|\/dev\/null|nul\b|\$null))/i.test(simple)) return "Writing files is not allowed in a read-only session";
@@ -15725,6 +16177,7 @@ function readOnlyCommandDenial(command) {
15725
16177
  continue;
15726
16178
  }
15727
16179
  if (name === "find" && args.some((a) => a === "-delete" || a === "-exec" || a === "-execdir")) return "find with -delete or -exec is not allowed in a read-only session";
16180
+ if (options.allowChecks && isCheck(name, args)) continue;
15728
16181
  if (!READ_ONLY_BINS.has(name)) return `${name || "This command"} is not on the read-only allowlist`;
15729
16182
  }
15730
16183
  }
@@ -15866,7 +16319,7 @@ function tempRootsFrom(candidates, platform, home) {
15866
16319
  }
15867
16320
  return out;
15868
16321
  }
15869
- const realpath = (p) => {
16322
+ const realpath$1 = (p) => {
15870
16323
  try {
15871
16324
  return realpathSync.native(p);
15872
16325
  } catch {
@@ -15883,7 +16336,7 @@ function osTempRoots(platform = process.platform) {
15883
16336
  if (platform !== process.platform) return [];
15884
16337
  if (cachedTempRoots) return cachedTempRoots;
15885
16338
  const env = process.env;
15886
- cachedTempRoots = tempRootsFrom([os.tmpdir(), ...platform === "win32" ? [env.TEMP, env.TMP] : [env.TMPDIR, "/tmp"]].flatMap((c) => c ? [c, realpath(c)] : []), platform, os.homedir());
16339
+ cachedTempRoots = tempRootsFrom([os.tmpdir(), ...platform === "win32" ? [env.TEMP, env.TMP] : [env.TMPDIR, "/tmp"]].flatMap((c) => c ? [c, realpath$1(c)] : []), platform, os.homedir());
15887
16340
  return cachedTempRoots;
15888
16341
  }
15889
16342
  const SECRET_BASENAMES = /* @__PURE__ */ new Set([
@@ -16739,6 +17192,8 @@ function approvalTitle(name, input) {
16739
17192
  // ../../packages/providers/src/claude/permissions.ts
16740
17193
  /**
16741
17194
  * Native permission mode per autonomy level (§11.2):
17195
+ * - read_only -> `default`; edits and writing commands are denied by the PreToolUse hook
17196
+ * (`ClaudePolicy.denial`), so they never reach a prompt
16742
17197
  * - supervised -> `default`, and every edit/command is forced to ask via a PreToolUse hook
16743
17198
  * - auto -> `acceptEdits`, plus the risky-action classifier
16744
17199
  * - full -> `bypassPermissions`, asking only for writes outside the worktree (the OS
@@ -16747,7 +17202,7 @@ function approvalTitle(name, input) {
16747
17202
  */
16748
17203
  function permissionModeFor(autonomy, planMode) {
16749
17204
  if (planMode) return "plan";
16750
- if (autonomy === "supervised") return "default";
17205
+ if (autonomy === "read_only" || autonomy === "supervised") return "default";
16751
17206
  if (autonomy === "auto") return "acceptEdits";
16752
17207
  return "bypassPermissions";
16753
17208
  }
@@ -16769,16 +17224,23 @@ var ClaudePolicy = class {
16769
17224
  get level() {
16770
17225
  return this.autonomy;
16771
17226
  }
17227
+ /**
17228
+ * True while edits and writing commands are refused: a read-only role (orchestrator, PMs),
17229
+ * or the `read_only` permission level, which can change live with `setAutonomy`.
17230
+ */
17231
+ get isReadOnly() {
17232
+ return this.readOnly || this.autonomy === "read_only";
17233
+ }
16772
17234
  /** Switch the level; the next `gate()` uses it. `allow_session` decisions are kept. */
16773
17235
  setAutonomy(level) {
16774
17236
  this.autonomy = level;
16775
17237
  }
16776
17238
  /** Why a call is refused outright (read-only sessions), or undefined. */
16777
17239
  denial(toolName, input) {
16778
- if (!this.readOnly) return void 0;
16779
- if (EDIT_TOOLS.has(toolName)) return "This session is read-only: it cannot edit files. Act through CodeDen tools instead.";
17240
+ if (!this.isReadOnly) return void 0;
17241
+ if (EDIT_TOOLS.has(toolName)) return this.readOnly ? "This session is read-only: it cannot edit files. Act through CodeDen tools instead." : "This session has read-only permissions: it cannot edit files, commit or push. Report what you found instead.";
16780
17242
  if (COMMAND_TOOLS.has(toolName)) {
16781
- const reason = readOnlyCommandDenial(str(input, "command") ?? "");
17243
+ const reason = readOnlyCommandDenial(str(input, "command") ?? "", { allowChecks: !this.readOnly });
16782
17244
  return reason ? `This session is read-only: ${reason}.` : void 0;
16783
17245
  }
16784
17246
  }
@@ -18649,7 +19111,8 @@ const capabilities$2 = {
18649
19111
  reasoningEffort: true,
18650
19112
  mcp: true,
18651
19113
  skills: true,
18652
- liveAutonomy: true
19114
+ liveAutonomy: true,
19115
+ readOnly: true
18653
19116
  };
18654
19117
  const EFFORTS = [
18655
19118
  "low",
@@ -19302,6 +19765,7 @@ async function handleServerRequest(req, ac) {
19302
19765
  // ../../packages/providers/src/codex/config.ts
19303
19766
  /**
19304
19767
  * Native sandbox + approval policy per autonomy level (§11.2):
19768
+ * - read_only -> read-only sandbox + `never`: writes fail instead of asking, no network
19305
19769
  * - supervised -> `untrusted`: every command that isn't known-safe and every patch asks
19306
19770
  * - auto -> workspace-write sandbox + `on-request`: the sandbox confines writes (to the
19307
19771
  * worktree, extra roots and the OS temp directory) and network; escaping it
@@ -19310,7 +19774,7 @@ async function handleServerRequest(req, ac) {
19310
19774
  * to the worktree, extra roots and temp
19311
19775
  */
19312
19776
  function codexPolicy(autonomy, readOnly = false) {
19313
- if (readOnly) return {
19777
+ if (readOnly || autonomy === "read_only") return {
19314
19778
  approvalPolicy: "never",
19315
19779
  sandbox: "read-only",
19316
19780
  networkAccess: false
@@ -19337,7 +19801,7 @@ function codexPolicy(autonomy, readOnly = false) {
19337
19801
  * never needs an approval (§11.2).
19338
19802
  */
19339
19803
  function codexWritableRoots(opts, tempRoots = osTempRoots()) {
19340
- const temp = opts.readOnly || opts.autonomy === "supervised" ? [] : tempRoots;
19804
+ const temp = opts.readOnly || opts.autonomy === "read_only" || opts.autonomy === "supervised" ? [] : tempRoots;
19341
19805
  return [.../* @__PURE__ */ new Set([...opts.additionalDirectories, ...temp])];
19342
19806
  }
19343
19807
  /**
@@ -20309,7 +20773,8 @@ const capabilities$1 = {
20309
20773
  reasoningEffort: true,
20310
20774
  mcp: true,
20311
20775
  skills: true,
20312
- liveAutonomy: false
20776
+ liveAutonomy: false,
20777
+ readOnly: true
20313
20778
  };
20314
20779
  const CONFIGURED_MODEL = "The model configured in ~/.codex/config.toml";
20315
20780
  /**
@@ -20582,7 +21047,8 @@ var FakeSession = class {
20582
21047
  await this.streamAssistant(turn, threadId, this.replyFor(prompt));
20583
21048
  await this.todoList(turn);
20584
21049
  if (prompt.includes("[mcp]")) await this.mcpToolCall(turn);
20585
- if ((this.fake.approval || prompt.includes("[approval]")) && this.autonomy !== "full" ? await this.approval(turn) : true) await this.commandToolCall(turn);
21050
+ const readOnly = this.autonomy === "read_only";
21051
+ if ((!readOnly && (this.fake.approval || prompt.includes("[approval]")) && this.autonomy !== "full" ? await this.approval(turn) : true) && !readOnly) await this.commandToolCall(turn);
20586
21052
  if (this.fake.subagent || prompt.includes("[subagent]")) await this.subagent(turn);
20587
21053
  if (this.fake.followups ?? true) {
20588
21054
  await this.step(turn);
@@ -20975,7 +21441,8 @@ const capabilities = {
20975
21441
  reasoningEffort: false,
20976
21442
  mcp: false,
20977
21443
  skills: false,
20978
- liveAutonomy: true
21444
+ liveAutonomy: true,
21445
+ readOnly: true
20979
21446
  };
20980
21447
  /**
20981
21448
  * Deterministic scripted provider for demos and tests (§7.2). Every turn streams:
@@ -26296,6 +26763,303 @@ async function listPorts(probe = []) {
26296
26763
  url: `http://localhost:${port}`
26297
26764
  }));
26298
26765
  }
26766
+ // ../runner/src/util/paths.ts
26767
+ /** True when `child` is `dir` itself or somewhere inside it. */
26768
+ function isInside(dir, child) {
26769
+ const rel = relative(resolve(dir), resolve(child));
26770
+ return rel === "" || !rel.startsWith("..") && !/^[a-zA-Z]:/.test(rel);
26771
+ }
26772
+ // ../runner/src/workspace/delete-tree.ts
26773
+ /** Error codes Windows returns while another process still holds a file or folder open. */
26774
+ const IN_USE = /* @__PURE__ */ new Set([
26775
+ "EBUSY",
26776
+ "EPERM",
26777
+ "EACCES",
26778
+ "ENOTEMPTY"
26779
+ ]);
26780
+ const errorCode = (error) => error && typeof error === "object" && "code" in error ? String(error.code) : void 0;
26781
+ /** Run `op`, retrying briefly (about 1.5s for 5 attempts) while the path is in use. */
26782
+ async function retry(op, attempts = 5) {
26783
+ for (let attempt = 1;; attempt++) try {
26784
+ await op();
26785
+ return;
26786
+ } catch (error) {
26787
+ const code = errorCode(error);
26788
+ if (code === "ENOENT") return;
26789
+ if (!code || !IN_USE.has(code) || attempt >= attempts) throw error;
26790
+ await new Promise((r) => setTimeout(r, 100 * attempt));
26791
+ }
26792
+ }
26793
+ /** Delete a file or link (never what a link points at); clears the read-only flag if needed. */
26794
+ function removeFile(path) {
26795
+ return retry(async () => {
26796
+ try {
26797
+ await unlink(path);
26798
+ } catch (error) {
26799
+ if (errorCode(error) !== "EPERM" && errorCode(error) !== "EACCES") throw error;
26800
+ await chmod(path, 438).catch(() => void 0);
26801
+ await unlink(path);
26802
+ }
26803
+ });
26804
+ }
26805
+ async function kind(path, entry) {
26806
+ if (entry.isSymbolicLink()) return "other";
26807
+ if (entry.isDirectory()) return "dir";
26808
+ if (entry.isFile()) return "other";
26809
+ const stat = await lstat(path).catch(() => void 0);
26810
+ return stat?.isDirectory() && !stat.isSymbolicLink() ? "dir" : "other";
26811
+ }
26812
+ /**
26813
+ * Empty `dir`: files and links are unlinked, real folders are emptied and removed. A symlink
26814
+ * or junction is never descended into (Windows reports junctions as links), so nothing
26815
+ * outside the tree is touched. Every branch is finished before the first failure is thrown.
26816
+ */
26817
+ async function emptyDir(dir) {
26818
+ let entries;
26819
+ try {
26820
+ entries = await readdir(dir, { withFileTypes: true });
26821
+ } catch (error) {
26822
+ if (errorCode(error) === "ENOENT") return;
26823
+ throw error;
26824
+ }
26825
+ const failed = (await Promise.allSettled(entries.map(async (entry) => {
26826
+ const path = join(dir, entry.name);
26827
+ if (await kind(path, entry) !== "dir") return removeFile(path);
26828
+ await emptyDir(path);
26829
+ await retry(() => rmdir(path));
26830
+ }))).find((r) => r.status === "rejected");
26831
+ if (failed) throw failed.reason;
26832
+ }
26833
+ /** A short, user-facing error for a failed delete or move of the worktree folder `root`. */
26834
+ function deleteFailed(root, error) {
26835
+ const file = error && typeof error === "object" && "path" in error ? String(error.path) : root;
26836
+ const where = relative(root, file);
26837
+ const code = errorCode(error);
26838
+ if (code && IN_USE.has(code)) return new CommandError("worktree_in_use", `${where ? `A file in the worktree is in use (${where})` : "The worktree is in use"}. Close whatever is using it (a terminal, dev server or editor), then try again.`);
26839
+ const reason = error instanceof Error ? error.message : String(error);
26840
+ return new CommandError("remove_failed", `Could not delete ${where || root}: ${reason}`);
26841
+ }
26842
+ /**
26843
+ * Delete a worktree folder without ever following a symlink or junction out of it.
26844
+ *
26845
+ * `git worktree remove` can't be trusted with this on Windows: Git treats junctions as
26846
+ * folders, so it deletes the contents of whatever a live junction points at, and it stops at
26847
+ * a dangling one with "Directory not empty". In a pnpm install the packages are deleted before
26848
+ * the junctions that point at them, so it fails halfway, after removing `.git`. Node's
26849
+ * recursive `rm` is no better here: on Windows it waits for as long as a file stays locked.
26850
+ *
26851
+ * `.git` goes first, so a delete that a locked file interrupts leaves an orphan folder the
26852
+ * next attempt cleans up (never a damaged worktree that looks dirty). Each locked entry is
26853
+ * retried for about 1.5s, then this throws `worktree_in_use`.
26854
+ */
26855
+ async function deleteTree(root) {
26856
+ try {
26857
+ const stat = await lstat(root).catch(() => void 0);
26858
+ if (!stat) return;
26859
+ if (stat.isSymbolicLink() || !stat.isDirectory()) return await removeFile(root);
26860
+ await removeFile(join(root, ".git"));
26861
+ await emptyDir(root);
26862
+ await retry(() => rmdir(root));
26863
+ } catch (error) {
26864
+ throw deleteFailed(root, error);
26865
+ }
26866
+ }
26867
+ // ../runner/src/workspace/trash.ts
26868
+ /**
26869
+ * True for a session folder CodeDen created and owns: exactly `<home>/wt/<project>/<session>`.
26870
+ * Only these may be deleted once they are no longer git worktrees.
26871
+ */
26872
+ function isManagedWorktreePath(home, path) {
26873
+ const rel = relative(resolve(home, "wt"), resolve(path));
26874
+ if (!rel || isAbsolute(rel)) return false;
26875
+ const parts = rel.split(/[\\/]+/);
26876
+ return parts.length === 2 && !parts.includes("..") && !parts.includes(".") && parts[0] !== ".trash";
26877
+ }
26878
+ /** Where removed worktrees wait to be deleted. `.trash` can never be a project slug. */
26879
+ function trashDir(home) {
26880
+ return join(resolve(home), "wt", ".trash");
26881
+ }
26882
+ /**
26883
+ * Move a session folder into the trash. A rename is all or nothing: while anything in the
26884
+ * folder is held open (a process's working directory, an open file), Windows refuses it and
26885
+ * the folder stays exactly as it was (`worktree_in_use`). Returns null when the trash is on
26886
+ * another volume.
26887
+ */
26888
+ async function moveToTrash(home, path) {
26889
+ const trash = trashDir(home);
26890
+ await mkdir(trash, { recursive: true });
26891
+ const stamp = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`;
26892
+ const dest = join(trash, `${basename(dirname(path))}-${basename(path)}-${stamp}`);
26893
+ try {
26894
+ await retry(() => rename(path, dest), 8);
26895
+ return dest;
26896
+ } catch (error) {
26897
+ if (errorCode(error) === "EXDEV") return null;
26898
+ throw deleteFailed(path, error);
26899
+ }
26900
+ }
26901
+ const runs = /* @__PURE__ */ new Map();
26902
+ /**
26903
+ * Delete everything in the trash, one folder at a time, never following a link (the links in
26904
+ * a trashed pnpm install still point at the old path, where a new worktree may live by now).
26905
+ * Runs in the background after each removal and when the runner starts; a folder something
26906
+ * still holds open stays for the next run. Calls during a run share it and add one more pass.
26907
+ */
26908
+ function emptyTrash(home, log) {
26909
+ const trash = trashDir(home);
26910
+ const current = runs.get(trash);
26911
+ if (current) {
26912
+ current.again = true;
26913
+ return current.done;
26914
+ }
26915
+ const run = {
26916
+ again: true,
26917
+ done: Promise.resolve()
26918
+ };
26919
+ run.done = (async () => {
26920
+ try {
26921
+ while (run.again) {
26922
+ run.again = false;
26923
+ const entries = await readdir(trash).catch(() => []);
26924
+ for (const name of entries) await deleteTree(join(trash, name)).catch((error) => log?.(`worktree trash: ${error instanceof Error ? error.message : String(error)}`));
26925
+ }
26926
+ } finally {
26927
+ runs.delete(trash);
26928
+ }
26929
+ })();
26930
+ runs.set(trash, run);
26931
+ return run.done;
26932
+ }
26933
+ /**
26934
+ * Delete a worktree folder that has been cleared for removal. A session folder under
26935
+ * `<home>/wt` goes to the trash, so the removal is instant and the path is free right away,
26936
+ * and is deleted in the background. Anything else is deleted in place.
26937
+ */
26938
+ async function discardFolder(home, path, log) {
26939
+ if (isManagedWorktreePath(home, path) ? await moveToTrash(home, path) : null) emptyTrash(home, log);
26940
+ else await deleteTree(path);
26941
+ }
26942
+ // ../runner/src/workspace/removal.ts
26943
+ async function canonical(path) {
26944
+ const real = await realpath(path).catch(() => resolve(path));
26945
+ return process.platform === "win32" ? real.toLowerCase() : real;
26946
+ }
26947
+ async function listWorktrees(repo) {
26948
+ const out = await gitText(repo, [
26949
+ "worktree",
26950
+ "list",
26951
+ "--porcelain"
26952
+ ]).catch(() => "");
26953
+ const list = [];
26954
+ for (const block of out.split(/\r?\n\r?\n/)) {
26955
+ const lines = block.split(/\r?\n/);
26956
+ if (!lines[0]?.startsWith("worktree ")) continue;
26957
+ const locked = lines.some((l) => l === "locked" || l.startsWith("locked "));
26958
+ list.push({
26959
+ path: lines[0].slice(9),
26960
+ locked
26961
+ });
26962
+ }
26963
+ return list;
26964
+ }
26965
+ /** Classify `path` against the worktrees of `repo` (see `WorktreeState`). */
26966
+ async function worktreeState(repo, path) {
26967
+ const target = await canonical(path);
26968
+ let registered = false;
26969
+ let locked = false;
26970
+ let containsOther = false;
26971
+ for (const wt of await listWorktrees(repo)) {
26972
+ const other = await canonical(wt.path);
26973
+ if (other === target) {
26974
+ registered = true;
26975
+ locked = wt.locked;
26976
+ } else if (isInside(target, other)) containsOther = true;
26977
+ }
26978
+ if (isInside(target, await canonical(repo))) containsOther = true;
26979
+ const info = {
26980
+ registered,
26981
+ locked,
26982
+ containsOther
26983
+ };
26984
+ const stat = await lstat(path).catch(() => void 0);
26985
+ if (!stat) return {
26986
+ state: "missing",
26987
+ ...info
26988
+ };
26989
+ if (stat.isSymbolicLink() || !stat.isDirectory()) return {
26990
+ state: "orphan",
26991
+ ...info
26992
+ };
26993
+ const dotGit = await lstat(resolve(path, ".git")).catch(() => void 0);
26994
+ if (!dotGit) return {
26995
+ state: "orphan",
26996
+ ...info
26997
+ };
26998
+ return {
26999
+ state: dotGit.isFile() && registered ? "registered" : "foreign",
27000
+ ...info
27001
+ };
27002
+ }
27003
+ /** Whether a registered worktree has uncommitted changes (ignored files don't count). */
27004
+ async function isDirty(path) {
27005
+ return (await git(path, ["status", "--porcelain"])).stdout.toString("utf8").trim() !== "";
27006
+ }
27007
+ /**
27008
+ * Decide whether `workspace.remove` may delete the path, before anything is touched (terminals
27009
+ * and processes in it are stopped only after this passes). Refuses (`not_a_worktree`) anything
27010
+ * that isn't a linked worktree of the repository, unless it is an orphan inside CodeDen's
27011
+ * worktree folder, and anything containing the repository or another worktree. Unless forced,
27012
+ * keeps a locked worktree (`worktree_locked`) or one with uncommitted changes
27013
+ * (`worktree_dirty`); ignored files (build output, `node_modules`) don't count.
27014
+ */
27015
+ async function assertRemovable(cmd, home) {
27016
+ const path = resolve(cmd.path);
27017
+ const info = await worktreeState(cmd.repoPath, path);
27018
+ const leftAlone = (why) => new CommandError("not_a_worktree", `${path} ${why}, so it was left alone`);
27019
+ if (info.state === "missing") return info;
27020
+ if (info.containsOther) throw leftAlone("contains the repository or another worktree");
27021
+ if (info.state === "foreign") throw leftAlone("is not a worktree of this repository");
27022
+ if (info.state === "orphan" && !isManagedWorktreePath(home, path)) throw leftAlone("is no longer a git worktree and is outside CodeDen's worktree folder");
27023
+ if (info.state !== "registered" || cmd.force) return info;
27024
+ if (info.locked) throw new CommandError("worktree_locked", `${path} is locked; unlock it or remove it with force`);
27025
+ if (await isDirty(path)) throw new CommandError("worktree_dirty", `${path} has uncommitted changes; commit them or remove it with force`);
27026
+ return info;
27027
+ }
27028
+ /** Drop git's records of worktrees whose folders are gone. */
27029
+ async function pruneWorktrees(repo) {
27030
+ await git(repo, ["worktree", "prune"]).catch(() => void 0);
27031
+ }
27032
+ /**
27033
+ * `workspace.remove`: delete a session worktree and git's record of it. Idempotent: a missing
27034
+ * path, or a half-deleted orphan inside `<home>/wt`, ends removed too. The folder goes to the
27035
+ * trash or is deleted in place, never through a link (`discardFolder`), then
27036
+ * `git worktree prune` drops the record. The branch stays unless `deleteBranch` names it.
27037
+ */
27038
+ async function removeWorkspace(cmd, home, log) {
27039
+ const path = resolve(cmd.path);
27040
+ const info = await assertRemovable(cmd, home);
27041
+ const registered = info.state === "registered";
27042
+ const discardedChanges = registered && cmd.force === true && await isDirty(path);
27043
+ try {
27044
+ if (registered && info.locked) await git(cmd.repoPath, [
27045
+ "worktree",
27046
+ "unlock",
27047
+ path
27048
+ ]).catch(() => void 0);
27049
+ if (info.state !== "missing") await discardFolder(home, path, log);
27050
+ } finally {
27051
+ await pruneWorktrees(cmd.repoPath);
27052
+ }
27053
+ if (cmd.deleteBranch) await git(cmd.repoPath, [
27054
+ "branch",
27055
+ "-D",
27056
+ cmd.deleteBranch
27057
+ ]).catch(() => void 0);
27058
+ return {
27059
+ removed: true,
27060
+ ...discardedChanges ? { discardedChanges } : {}
27061
+ };
27062
+ }
26299
27063
  // ../runner/src/workspace/project-config.ts
26300
27064
  const PROJECT_CONFIG_PATH = ".codeden/project.json";
26301
27065
  /**
@@ -26438,20 +27202,41 @@ async function resolveBase(repo, cmd) {
26438
27202
  ])) return `origin/${def}`;
26439
27203
  return def ?? "HEAD";
26440
27204
  }
26441
- async function createWorktree(cmd, home) {
27205
+ function isEmptyDir(path) {
27206
+ try {
27207
+ return readdirSync(path).length === 0;
27208
+ } catch {
27209
+ return false;
27210
+ }
27211
+ }
27212
+ /**
27213
+ * Make `path` ready for `git worktree add`: reuse a worktree of this repository, delete the
27214
+ * orphan folder an interrupted removal left inside `<home>/wt` (stopping processes in it
27215
+ * first), and drop git's stale record of it. Returns the worktree to reuse, if any.
27216
+ */
27217
+ async function clearTarget(repo, path, ctx) {
27218
+ const info = await worktreeState(repo, path);
27219
+ if (info.state === "registered") return {
27220
+ cwd: path,
27221
+ branch: await currentBranch(path),
27222
+ created: false
27223
+ };
27224
+ if (info.state === "orphan" && isManagedWorktreePath(ctx.home, path) && !info.containsOther) {
27225
+ await ctx.supervisor.killUnder(path);
27226
+ await discardFolder(ctx.home, path);
27227
+ } else if (info.state !== "missing" && !isEmptyDir(path)) throw new CommandError("path_exists", `${path} exists and is not a worktree`);
27228
+ if (info.registered) await pruneWorktrees(repo);
27229
+ return null;
27230
+ }
27231
+ async function createWorktree(cmd, ctx) {
26442
27232
  const repo = await topLevel(cmd.repoPath).catch(() => {
26443
27233
  throw new CommandError("not_a_repo", `${cmd.repoPath} is not a git repository`);
26444
27234
  });
26445
27235
  const short = sessionShort(cmd.sessionId);
26446
- const path = resolve(cmd.path ?? join(home, "wt", cmd.projectSlug ?? slugFromPath(repo), short));
26447
- if (existsSync(path)) {
26448
- if (await isWorkTree(path)) return {
26449
- cwd: path,
26450
- branch: await currentBranch(path),
26451
- created: false
26452
- };
26453
- if (readdirSync(path).length > 0) throw new CommandError("path_exists", `${path} exists and is not a worktree`);
26454
- }
27236
+ const slug = cmd.projectSlug ?? slugFromPath(repo);
27237
+ const path = resolve(cmd.path ?? join(ctx.home, "wt", slug, short));
27238
+ const reuse = await clearTarget(repo, path, ctx);
27239
+ if (reuse) return reuse;
26455
27240
  await mkdir(dirname(path), { recursive: true });
26456
27241
  const base = await resolveBase(repo, cmd);
26457
27242
  if (cmd.branch && await branchExists(repo, cmd.branch)) {
@@ -26511,7 +27296,7 @@ async function prepareWorkspace(cmd, ctx) {
26511
27296
  created: false
26512
27297
  };
26513
27298
  break;
26514
- case "worktree": target = await createWorktree(cmd, ctx.home);
27299
+ case "worktree": target = await createWorktree(cmd, ctx);
26515
27300
  }
26516
27301
  const { config, error } = await readProjectConfig(target.cwd);
26517
27302
  if (error) ctx.emit({
@@ -26536,53 +27321,6 @@ async function prepareWorkspace(cmd, ctx) {
26536
27321
  setup
26537
27322
  };
26538
27323
  }
26539
- /**
26540
- * Refuse to remove a worktree with uncommitted changes (`worktree_dirty`) unless forced. Runs
26541
- * before terminals and processes in the worktree are stopped, so a dirty worktree is left
26542
- * exactly as it was. Ignored files (build output, `node_modules`) don't count.
26543
- */
26544
- async function assertRemovable(cmd) {
26545
- if (cmd.force || !existsSync(cmd.path) || !await isWorkTree(cmd.path)) return;
26546
- if ((await git(cmd.path, ["status", "--porcelain"])).stdout.toString("utf8").trim()) throw new CommandError("worktree_dirty", `${cmd.path} has uncommitted changes; commit them or remove it with force`);
26547
- }
26548
- /** `workspace.remove`: remove a worktree (retrying briefly for Windows file locks). */
26549
- async function removeWorkspace(cmd) {
26550
- const path = resolve(cmd.path);
26551
- let removed = false;
26552
- let lastError;
26553
- for (let attempt = 0; attempt < 5 && !removed; attempt++) try {
26554
- const args = [
26555
- "worktree",
26556
- "remove",
26557
- ...cmd.force ? ["--force"] : [],
26558
- path
26559
- ];
26560
- await git(cmd.repoPath, args);
26561
- removed = true;
26562
- } catch (error) {
26563
- lastError = error;
26564
- if (!existsSync(path)) break;
26565
- if (!cmd.force && /modified or untracked|contains/.test(String(error))) break;
26566
- await new Promise((r) => setTimeout(r, 250 * (attempt + 1)));
26567
- }
26568
- if (!removed && existsSync(path) && cmd.force) {
26569
- await rm(path, {
26570
- recursive: true,
26571
- force: true,
26572
- maxRetries: 5,
26573
- retryDelay: 200
26574
- });
26575
- removed = true;
26576
- }
26577
- await git(cmd.repoPath, ["worktree", "prune"]).catch(() => void 0);
26578
- if (!removed && existsSync(path)) throw lastError instanceof Error ? lastError : new CommandError("remove_failed", `Could not remove ${path}`);
26579
- if (cmd.deleteBranch) await git(cmd.repoPath, [
26580
- "branch",
26581
- "-D",
26582
- cmd.deleteBranch
26583
- ]).catch(() => void 0);
26584
- return { removed: removed || !existsSync(path) };
26585
- }
26586
27324
  // ../runner/src/commands.ts
26587
27325
  /** Execute one server command (§4.3). Throws `CommandError` for expected failures. */
26588
27326
  async function executeCommand(core, command) {
@@ -26632,10 +27370,10 @@ async function executeCommand(core, command) {
26632
27370
  emit: core.emit
26633
27371
  });
26634
27372
  case "workspace.remove":
26635
- await assertRemovable(command);
27373
+ await assertRemovable(command, core.home);
26636
27374
  await terminals.closeUnder(command.path);
26637
27375
  await core.supervisor.killUnder(command.path);
26638
- return removeWorkspace(command);
27376
+ return removeWorkspace(command, core.home, core.log);
26639
27377
  case "session.start":
26640
27378
  if (core.isDraining()) throw new CommandError("draining", "Runner is draining");
26641
27379
  return host.start(command);
@@ -26913,12 +27651,6 @@ async function handleMcpRequest(request, upstream) {
26913
27651
  return jsonRpcError(502, `CodeDen server unreachable: ${error.message}`);
26914
27652
  }
26915
27653
  }
26916
- // ../runner/src/util/paths.ts
26917
- /** True when `child` is `dir` itself or somewhere inside it. */
26918
- function isInside(dir, child) {
26919
- const rel = relative(resolve(dir), resolve(child));
26920
- return rel === "" || !rel.startsWith("..") && !/^[a-zA-Z]:/.test(rel);
26921
- }
26922
27654
  // ../runner/src/process/supervisor.ts
26923
27655
  /**
26924
27656
  * Tracks every child process the runner starts so they can be killed as whole process
@@ -28721,6 +29453,7 @@ async function startRunner(opts) {
28721
29453
  browser,
28722
29454
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
28723
29455
  emit,
29456
+ log,
28724
29457
  isDraining: () => draining,
28725
29458
  drain: ({ reason, timeoutMs, exitWhenDrained }) => {
28726
29459
  if (!draining) {
@@ -28794,6 +29527,7 @@ async function startRunner(opts) {
28794
29527
  renameSync(tmp, lockFile);
28795
29528
  log(`runner ${journal.runnerId} listening on 127.0.0.1:${port}`);
28796
29529
  registry.refreshModels();
29530
+ emptyTrash(opts.home, log);
28797
29531
  const removeLock = () => removeOwnLock(lockFile);
28798
29532
  process.once("exit", removeLock);
28799
29533
  async function close() {
@@ -28824,6 +29558,6 @@ async function startRunner(opts) {
28824
29558
  close
28825
29559
  };
28826
29560
  }
28827
- export { ListAutomationRunsQuery as $, literal as $n, FactoryTuning as $t, SessionFileQuery as A, FactoryLimits as An, LocalSttModelParams as At, ImageTestRequest as B, McpServerUpsertRequest as Bn, CreateGithubIssueRequest as Bt, isElectronRuntime as C, ProjectConfigFile as Cn, ThreadItemsQuery as Ct, CODEDEN_VERSION as D, Project as Dn, agentEventToNewEvent as Dt, wrapper_default as E, WorkKey as En, HealthResponse as Et, ListConflictsQuery as F, AgentConfigListQuery as Fn, UpdateStatus as Ft, BrowserNavigateRequest as G, _enum as Gn, GithubPr as Gt, BrowserClientMessage as H, ProviderId as Hn, GithubIssue as Ht, ListNoticesQuery as I, AgentConfigLocator as In, compareVersions$1 as It, BrowserViewportRequest as J, boolean as Jn, githubPrId as Jt, BrowserNewTabRequest as K, _null as Kn, MergePrRequest as Kt, ResolveConflictRequest as L, AgentConfigWriteRequest as Ln, isNewerVersion as Lt, RunnerToServerMessage as M, InboxItemKind as Mn, TtsRequest as Mt, ServerLock as N, AgentConfigDeleteQuery as Nn, AppPointer as Nt, UsageQuery as O, sortProjects as On, Settings as Ot, ArchiveSessionRequest as P, AgentConfigDiffRequest as Pn, UpdateInstallRequest as Pt, CreateAutomationRequest as Q, intersection as Qn, FactoryTriageRequest as Qt, RoleMessageRequest as R, McpServerDiffRequest as Rn, ConflictRecord as Rt, acquireInstallLock as S, SessionOptions as Sn, SteerRequest as St, import_websocket_server as T, StartedBy as Tn, UpdateProjectRequest as Tt, BrowserDevServerRequest as U, ReasoningEffort as Un, GithubLabelRequest as Ut, summarizeActivity as V, AutonomyLevel as Vn, GithubCommentRequest as Vt, BrowserHistoryRequest as W, datetime as Wn, GithubListQuery as Wt, AutomationFileFrontmatter as X, discriminatedUnion as Xn, DiscoveryArea as Xt, BrowserWsQuery as Y, custom as Yn, parseGithubPrId as Yt, AutomationProjectRequest as Z, int as Zn, FactoryCandidate as Zt, importClaudeSdkFrom as _, SessionResult as _n, SecretKey as _t, Hono as a, Artifact as an, record as ar, TriageReport as at, runNpm as b, TerminalQuery as bn, SetSecretRequest as bt, serve as c, AutomationRun as cn, unknown as cr, UpdateAutomationRequest as ct, createImageEngineRegistry as d, SessionStatus as dn, safeParse$1 as dr, FileWriteRequest as dt, KillSwitchRequest as en, looseObject as er, ListAutomationsQuery as et, IMAGE_ASPECT_RATIOS as f, DiffScope as fn, prettifyError as fr, ListInboxQuery as ft, CLAUDE_SDK_VERSION as g, RestoreFilesRequest as gn, RespondRequest as gt, claudeSdkSource as h, GitPushRequest as hn, ResolveInboxRequest as ht, killTree as i, isFactoryMode as in, preprocess as ir, TriageDecisionRequest as it, RunnerLock as j, InboxItem as jn, SttQuery as jt, UsageSettings as k, DEFAULT_VISION_PATH as kn, LOCAL_STT_SAMPLE_RATE as kt, upgradeWebSocket as l, TriagePolicy as ln, ZodError as lr, CreateProjectRequest as lt, ImageGenerationError as m, GitCommitRequest as mn, ReorderProjectsRequest as mt, readLock as n, ReviewVerdict as nn, object as nr, SetAutomationEnabledRequest as nt, tryDecodeURIComponent as o, Automation as on, string as or, UpcomingRunsQuery as ot, resolveImageOutput as p, EditQueuedMessageRequest as pn, ListSessionsQuery as pt, BrowserOpenRequest as q, array as qn, TriageIssueRequest as qt, isAlive as r, SetAutonomyModeRequest as rn, optional as rr, StarterSetRequest as rt, bufferToFormData as s, AutomationGuards as sn, union as sr, UpdateAutomationProjectSettingsRequest as st, startRunner as t, PutVisionRequest as tn, number$1 as tr, RunAutomationRequest as tt, ImageEngineRegistry as u, Session as un, toJSONSchema as ur, FileReadQuery as ut, installClaudeSdk as v, SpeechSummaryRequest as vn, SendMessageRequest as vt, ownNodeEnv as w, ConflictDecision as wn, UiClientMessage as wt, resolveNpm as x, RunnerCommandResults as xn, StartSessionRequest as xt, isClaudeSdkInstalled as y, TerminalClientMessage as yn, SessionStartMeta as yt, UpdateSessionRequest as z, McpServerTarget as zn, NOTICE_BATCH_CODE as zt };
29561
+ export { BrowserNavigateRequest as $, AgentConfigLocator as $n, NOTICE_BATCH_CODE as $t, SessionFileQuery as A, GitCommitRequest as An, RespondRequest as At, NetworkStatus as B, ConflictDecision as Bn, HealthResponse as Bt, isElectronRuntime as C, AutomationRun as Cn, string as Cr, CreateProjectRequest as Ct, CODEDEN_VERSION as D, SessionStatus as Dn, toJSONSchema as Dr, ListSessionsQuery as Dt, wrapper_default as E, Session as En, ZodError as Er, ListInboxQuery as Et, ListConflictsQuery as F, TerminalClientMessage as Fn, StartSessionRequest as Ft, SetNetworkPasswordResponse as G, DEFAULT_VISION_PATH as Gn, SttQuery as Gt, RemoteAccessFile as H, WorkKey as Hn, Settings as Ht, ListNoticesQuery as I, TerminalQuery as In, SteerRequest as It, ImageTestRequest as J, InboxItemKind as Jn, UpdateInstallRequest as Jt, SignInRequest as K, FactoryLimits as Kn, TtsRequest as Kt, ResolveConflictRequest as L, RunnerCommandResults as Ln, ThreadItemsQuery as Lt, RunnerToServerMessage as M, RestoreFilesRequest as Mn, SendMessageRequest as Mt, ServerLock as N, SessionResult as Nn, SessionStartMeta as Nt, UsageQuery as O, DiffScope as On, safeParse$1 as Or, ReorderProjectsRequest as Ot, ArchiveSessionRequest as P, SpeechSummaryRequest as Pn, SetSecretRequest as Pt, BrowserHistoryRequest as Q, AgentConfigListQuery as Qn, ConflictRecord as Qt, RoleMessageRequest as R, SessionOptions as Rn, UiClientMessage as Rt, acquireInstallLock as S, AutomationGuards as Sn, record as Sr, UpdateAutomationRequest as St, import_websocket_server as T, TriagePolicy as Tn, unknown as Tr, FileWriteRequest as Tt, SetNetworkAccessRequest as U, Project as Un, LOCAL_STT_SAMPLE_RATE as Ut, PASSWORD_RULE_TEXT as V, StartedBy as Vn, agentEventToNewEvent as Vt, SetNetworkPasswordRequest as W, sortProjects as Wn, LocalSttModelParams as Wt, BrowserClientMessage as X, AgentConfigDeleteQuery as Xn, compareVersions$1 as Xt, summarizeActivity as Y, messageFrom as Yn, UpdateStatus as Yt, BrowserDevServerRequest as Z, AgentConfigDiffRequest as Zn, isNewerVersion as Zt, importClaudeSdkFrom as _, SetAutonomyModeRequest as _n, looseObject as _r, StarterSetRequest as _t, Hono as a, GithubPr as an, ProviderId as ar, AutomationImportSource as at, runNpm as b, Automation as bn, optional as br, UpcomingRunsQuery as bt, createAdaptorServer as c, githubPrId as cn, _enum as cr, ImportAutomationsRequest as ct, createImageEngineRegistry as d, FactoryCandidate as dn, boolean as dr, AutomationProjectRequest as dt, CreateGithubIssueRequest as en, AgentConfigWriteRequest as er, BrowserNewTabRequest as et, IMAGE_ASPECT_RATIOS as f, FactoryTriageRequest as fn, custom as fr, CreateAutomationRequest as ft, CLAUDE_SDK_VERSION as g, ReviewVerdict as gn, literal as gr, SetAutomationEnabledRequest as gt, claudeSdkSource as h, PutVisionRequest as hn, intersection as hr, RunAutomationRequest as ht, killTree as i, GithubListQuery as in, AutonomyLevel as ir, AUTOMATION_IMPORT_LIMITS as it, RunnerLock as j, GitPushRequest as jn, SecretKey as jt, UsageSettings as k, EditQueuedMessageRequest as kn, prettifyError as kr, ResolveInboxRequest as kt, upgradeWebSocket as l, parseGithubPrId as ln, _null as lr, AutomationFileFrontmatter as lt, ImageGenerationError as m, KillSwitchRequest as mn, int as mr, ListAutomationsQuery as mt, readLock as n, GithubIssue as nn, McpServerTarget as nr, BrowserViewportRequest as nt, tryDecodeURIComponent as o, MergePrRequest as on, ReasoningEffort as or, DuplicateAutomationRequest as ot, resolveImageOutput as p, FactoryTuning as pn, discriminatedUnion as pr, ListAutomationRunsQuery as pt, passwordProblem as q, InboxItem as qn, AppPointer as qt, isAlive as r, GithubLabelRequest as rn, McpServerUpsertRequest as rr, BrowserWsQuery as rt, bufferToFormData as s, TriageIssueRequest as sn, datetime as sr, ExportAutomationsQuery as st, startRunner as t, GithubCommentRequest as tn, McpServerDiffRequest as tr, BrowserOpenRequest as tt, ImageEngineRegistry as u, DiscoveryArea as un, array as ur, AutomationGuardsPatch as ut, installClaudeSdk as v, isFactoryMode as vn, number$1 as vr, TriageDecisionRequest as vt, ownNodeEnv as w, AutomationTrigger as wn, union as wr, FileReadQuery as wt, resolveNpm as x, AutomationAction as xn, preprocess as xr, UpdateAutomationProjectSettingsRequest as xt, isClaudeSdkInstalled as y, Artifact as yn, object as yr, TriageReport as yt, UpdateSessionRequest as z, ProjectConfigFile as zn, UpdateProjectRequest as zt };
28828
29562
 
28829
- //# sourceMappingURL=runner-CJetH9jW.js.map
29563
+ //# sourceMappingURL=runner-QPAnitMx.js.map