@codeuxai/codeux 0.8.1 → 0.8.3

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 (38) hide show
  1. package/README.md +128 -45
  2. package/dashboard/dist/assets/{AgentAvatarScene-DAjdNR8q.js → AgentAvatarScene-CdYNjBsU.js} +1 -1
  3. package/dashboard/dist/assets/{AgentsPage-dG7HaQww.js → AgentsPage-BKJ1Zdbv.js} +2 -2
  4. package/dashboard/dist/assets/{BackgroundManager-CxjNC7ME.js → BackgroundManager-B4A4jACF.js} +1 -1
  5. package/dashboard/dist/assets/{BranchNameSchemeEditor-wfv629Ml.js → BranchNameSchemeEditor-DfnH2VYW.js} +1 -1
  6. package/dashboard/dist/assets/{BrowserPage-5eFh6pZt.js → BrowserPage-BxfAsG3M.js} +1 -1
  7. package/dashboard/dist/assets/{ChatPage-CfunB4-0.js → ChatPage-MXugCDvS.js} +1 -1
  8. package/dashboard/dist/assets/{ErrorPage-CEHtK2Hk.js → ErrorPage-Y-fEDzly.js} +1 -1
  9. package/dashboard/dist/assets/{FileBrowserPage-BOVIkrrv.js → FileBrowserPage-B4JEdW9B.js} +1 -1
  10. package/dashboard/dist/assets/{KnowledgePage-CeKSiAys.js → KnowledgePage-DVbW7o61.js} +1 -1
  11. package/dashboard/dist/assets/{ListWindowSelector-cshzGFot.js → ListWindowSelector-CPMgebqM.js} +1 -1
  12. package/dashboard/dist/assets/{MemoryPage-Cctk6NFp.js → MemoryPage-C3RWZJL5.js} +1 -1
  13. package/dashboard/dist/assets/{OverviewTelemetry-D_V7-M2H.js → OverviewTelemetry-Cy9vzhdY.js} +1 -1
  14. package/dashboard/dist/assets/{ProjectsPage-D8ICYJVv.js → ProjectsPage-RmR2LMuR.js} +1 -1
  15. package/dashboard/dist/assets/{SchedulerPage-BM0sGrEZ.js → SchedulerPage-Bg08X7Tm.js} +1 -1
  16. package/dashboard/dist/assets/{SettingsPage-BlDplYC7.js → SettingsPage-DzE_F7Kw.js} +1 -1
  17. package/dashboard/dist/assets/{SprintBoatRace-DaP10JYZ.js → SprintBoatRace-BeZOItMO.js} +1 -1
  18. package/dashboard/dist/assets/{SprintDag-DbxJdjBK.js → SprintDag-vF8f33gn.js} +1 -1
  19. package/dashboard/dist/assets/{SprintsPage-XGhd04Sn.js → SprintsPage-bRHQPfWP.js} +1 -1
  20. package/dashboard/dist/assets/{StatsPage-CrSwtTGq.js → StatsPage-BZl_tzUr.js} +1 -1
  21. package/dashboard/dist/assets/{TasksPage-DNpVBxt6.js → TasksPage-rLMlkgxX.js} +1 -1
  22. package/dashboard/dist/assets/{index-BViPTeJx.js → index-DdNzHP9F.js} +5 -5
  23. package/dashboard/dist/assets/{monaco.contribution-KtWzfVt5.js → monaco.contribution-2KFAi0p0.js} +2 -2
  24. package/dashboard/dist/assets/{tsMode-CalcmZZa.js → tsMode-dZI5bz0O.js} +1 -1
  25. package/dashboard/dist/index.html +1 -1
  26. package/dist/infrastructure/providers/cli/provider-logs/opencode-log-parser.js +174 -20
  27. package/dist/infrastructure/providers/cli/provider-runner.js +51 -0
  28. package/dist/infrastructure/providers/cli/provider-usage.js +39 -6
  29. package/dist/infrastructure/providers/cli/workspace-artifact-service.js +17 -7
  30. package/dist/repositories/execution-repository.js +77 -0
  31. package/dist/services/provider-execution-service.js +10 -1
  32. package/dist/services/runtime-startup-recovery-service.js +170 -0
  33. package/dist/services/sprint-task-dispatch-service.js +7 -0
  34. package/docs/getting-started/quickstart.md +14 -3
  35. package/docs/operations/runbook.md +2 -0
  36. package/docs/settings/configuration-and-storage.md +2 -0
  37. package/package.json +1 -1
  38. package/.code-ux/debug.log +0 -97190
@@ -5,9 +5,11 @@ import { RECOVERED_STALE_QA_SUMMARY_PREFIX } from "../domain/qa-review/qa-review
5
5
  const ACTIVE_SPRINT_RUN_STATUSES = ["queued", "running"];
6
6
  const ACTIVE_DISPATCH_STATUSES = ["queued", "claimed", "running", "cancel_requested"];
7
7
  const TERMINAL_TASK_RUN_STATES = new Set(["COMPLETED", "FAILED", "BLOCKED", "QUOTA"]);
8
+ const TERMINAL_SPRINT_RUN_STATUSES = new Set(["completed", "failed", "cancelled"]);
8
9
  const ACTIVE_TASK_RUN_STATES = ["PENDING", "RUNNING", "PAUSED"];
9
10
  const TASK_CODING_INVOCATION_TYPES = ["task_coding", "cli_task_coding", "cli_task_followup"];
10
11
  const CLI_PROVIDERS = new Set(["gemini", "codex", "claude-code", "qwen-code", "opencode"]);
12
+ const DURABLE_REMOTE_PROVIDERS = new Set(["jules"]);
11
13
  const QA_RUN_START_TIMEOUT_MS = 60_000;
12
14
  export class RuntimeStartupRecoveryService {
13
15
  deps;
@@ -25,6 +27,7 @@ export class RuntimeStartupRecoveryService {
25
27
  const reconciledContainerInvocationIds = await this.reconcileInterruptedCliInvocations(new Set(recoveredCliSessionIds));
26
28
  const reconciledQaReviewRunIds = await this.reconcileInterruptedQaReviewRuns();
27
29
  const reconciledStructuredInvocationIds = await this.reconcileInterruptedStructuredInvocations();
30
+ const rehydratedSprintRunIds = this.rehydrateDurableProviderSprintRuns();
28
31
  const reconciledTaskCodingInvocationIds = await this.reconcileInterruptedTaskCodingInvocations();
29
32
  const reconciledTaskCodingProviderIds = this.reconcileOrphanedTaskCodingProviderInvocations();
30
33
  const reconciledTaskRunIds = this.reconcileInterruptedTaskRuns();
@@ -39,6 +42,7 @@ export class RuntimeStartupRecoveryService {
39
42
  || reconciledStructuredInvocationIds.length > 0
40
43
  || reconciledTaskCodingInvocationIds.length > 0
41
44
  || reconciledTaskCodingProviderIds.length > 0
45
+ || rehydratedSprintRunIds.length > 0
42
46
  || reconciledTaskRunIds.length > 0
43
47
  || reconciledPausedSprintRunIds.length > 0
44
48
  || resumedSprintRunIds.length > 0
@@ -53,6 +57,7 @@ export class RuntimeStartupRecoveryService {
53
57
  reconciledStructuredInvocations: reconciledStructuredInvocationIds.length,
54
58
  reconciledTaskCodingInvocations: reconciledTaskCodingInvocationIds.length,
55
59
  reconciledTaskCodingProviders: reconciledTaskCodingProviderIds.length,
60
+ rehydratedSprintRuns: rehydratedSprintRunIds.length,
56
61
  reconciledTaskRuns: reconciledTaskRunIds.length,
57
62
  reconciledPausedSprintRuns: reconciledPausedSprintRunIds.length,
58
63
  resumedSprintRuns: resumedSprintRunIds.length,
@@ -69,6 +74,7 @@ export class RuntimeStartupRecoveryService {
69
74
  reconciledStructuredInvocationIds,
70
75
  reconciledTaskCodingInvocationIds,
71
76
  reconciledTaskCodingProviderIds,
77
+ rehydratedSprintRunIds,
72
78
  reconciledTaskRunIds,
73
79
  reconciledPausedSprintRunIds,
74
80
  resumedSprintRunIds,
@@ -426,6 +432,170 @@ export class RuntimeStartupRecoveryService {
426
432
  }
427
433
  return null;
428
434
  }
435
+ rehydrateDurableProviderSprintRuns() {
436
+ const executionRepository = this.deps.executionRepository;
437
+ if (typeof executionRepository.listTaskRunsByStates !== "function"
438
+ || typeof executionRepository.reassignTaskRunSprintRun !== "function"
439
+ || typeof executionRepository.reassignTaskDispatchSprintRun !== "function"
440
+ || typeof executionRepository.associateProviderInvocationRuntime !== "function") {
441
+ return [];
442
+ }
443
+ const durableTaskRuns = executionRepository.listTaskRunsByStates([...ACTIVE_TASK_RUN_STATES])
444
+ .filter((taskRun) => this.isRecoverableDurableProviderTaskRun(taskRun));
445
+ if (durableTaskRuns.length === 0) {
446
+ return [];
447
+ }
448
+ const taskRunsBySprintId = new Map();
449
+ for (const taskRun of durableTaskRuns) {
450
+ const entries = taskRunsBySprintId.get(taskRun.sprintId) || [];
451
+ entries.push(taskRun);
452
+ taskRunsBySprintId.set(taskRun.sprintId, entries);
453
+ }
454
+ const now = new Date().toISOString();
455
+ const rehydratedSprintRunIds = new Set();
456
+ for (const [sprintId, taskRuns] of taskRunsBySprintId.entries()) {
457
+ const rawStatus = this.deps.projectManagementRepository.getRawSprintStatus(sprintId);
458
+ if (rawStatus === null || rawStatus === "completed" || rawStatus === "cancelled") {
459
+ continue;
460
+ }
461
+ const targetRun = this.resolveDurableProviderRecoverySprintRun(taskRuns);
462
+ if (!targetRun) {
463
+ continue;
464
+ }
465
+ const targetRunWasTerminal = TERMINAL_SPRINT_RUN_STATUSES.has(targetRun.status);
466
+ if (TERMINAL_SPRINT_RUN_STATUSES.has(targetRun.status)) {
467
+ this.deps.executionRepository.updateSprintRun(targetRun.id, {
468
+ status: "running",
469
+ startedAt: targetRun.startedAt || now,
470
+ finishedAt: null,
471
+ lastHeartbeatAt: now,
472
+ });
473
+ this.deps.executionRepository.appendSprintRunEvent(targetRun.id, "sprint_rehydrated", "system", {
474
+ reason: "durable_provider_sessions_survived_restart",
475
+ previousStatus: targetRun.status,
476
+ durableProvider: "jules",
477
+ recoveredTaskRunCount: taskRuns.length,
478
+ }, {
479
+ sourceEventKey: `startup-recovery:durable-provider-sprint:${targetRun.id}`,
480
+ });
481
+ rehydratedSprintRunIds.add(targetRun.id);
482
+ }
483
+ for (const originalTaskRun of taskRuns) {
484
+ let taskRehydrated = targetRunWasTerminal;
485
+ const taskRun = originalTaskRun.sprintRunId === targetRun.id
486
+ ? originalTaskRun
487
+ : executionRepository.reassignTaskRunSprintRun(originalTaskRun.id, targetRun.id);
488
+ if (originalTaskRun.sprintRunId !== targetRun.id) {
489
+ rehydratedSprintRunIds.add(targetRun.id);
490
+ taskRehydrated = true;
491
+ }
492
+ let dispatchId = taskRun.dispatchId;
493
+ if (dispatchId) {
494
+ const dispatch = this.deps.executionRepository.getTaskDispatch(dispatchId);
495
+ if (dispatch) {
496
+ if (dispatch.sprintRunId !== targetRun.id) {
497
+ executionRepository.reassignTaskDispatchSprintRun(dispatch.id, targetRun.id);
498
+ rehydratedSprintRunIds.add(targetRun.id);
499
+ taskRehydrated = true;
500
+ }
501
+ if (!ACTIVE_DISPATCH_STATUSES.includes(dispatch.status)) {
502
+ this.deps.executionRepository.updateTaskDispatch(dispatch.id, {
503
+ status: this.resolveRehydratedDispatchStatus(taskRun),
504
+ startedAt: dispatch.startedAt || taskRun.startedAt || now,
505
+ finishedAt: null,
506
+ lastHeartbeatAt: now,
507
+ errorMessage: null,
508
+ });
509
+ rehydratedSprintRunIds.add(targetRun.id);
510
+ taskRehydrated = true;
511
+ }
512
+ }
513
+ else {
514
+ dispatchId = null;
515
+ }
516
+ }
517
+ if (!taskRehydrated) {
518
+ continue;
519
+ }
520
+ const sessionKey = this.resolveTaskRunSessionKey(taskRun);
521
+ const usage = sessionKey
522
+ ? this.deps.executionRepository.getLatestProviderInvocationUsageBySession(sessionKey, "task_coding")
523
+ : null;
524
+ if (usage) {
525
+ executionRepository.associateProviderInvocationRuntime(usage.id, {
526
+ sprintRunId: targetRun.id,
527
+ dispatchId,
528
+ taskRunId: taskRun.id,
529
+ });
530
+ if (usage.status !== "running") {
531
+ this.deps.executionRepository.updateProviderInvocationUsage(usage.id, {
532
+ status: "running",
533
+ finishedAt: null,
534
+ durationMs: null,
535
+ });
536
+ }
537
+ }
538
+ this.deps.executionRepository.appendTaskRunEvent(taskRun.id, "task_run_rehydrated", "system", {
539
+ reason: "durable_provider_session_survived_restart",
540
+ previousSprintRunId: originalTaskRun.sprintRunId,
541
+ sprintRunId: targetRun.id,
542
+ dispatchId,
543
+ provider: taskRun.provider,
544
+ sessionId: taskRun.sessionId,
545
+ }, {
546
+ sourceEventKey: `startup-recovery:durable-task-run:${taskRun.id}:${targetRun.id}`,
547
+ });
548
+ }
549
+ }
550
+ return [...rehydratedSprintRunIds];
551
+ }
552
+ isRecoverableDurableProviderTaskRun(taskRun) {
553
+ return DURABLE_REMOTE_PROVIDERS.has(String(taskRun.provider || ""))
554
+ && taskRun.mode === "jules"
555
+ && Boolean(this.resolveTaskRunSessionKey(taskRun))
556
+ && !isTerminalTaskRunState(taskRun);
557
+ }
558
+ resolveDurableProviderRecoverySprintRun(taskRuns) {
559
+ const firstTaskRun = taskRuns[0];
560
+ if (!firstTaskRun) {
561
+ return null;
562
+ }
563
+ const activeRun = this.deps.executionRepository.findActiveSprintRun(firstTaskRun.projectId, firstTaskRun.sprintId);
564
+ if (activeRun) {
565
+ return activeRun;
566
+ }
567
+ const candidateRuns = new Map();
568
+ for (const taskRun of taskRuns) {
569
+ if (!taskRun.sprintRunId) {
570
+ continue;
571
+ }
572
+ const sprintRun = this.deps.executionRepository.getSprintRun(taskRun.sprintRunId);
573
+ if (sprintRun) {
574
+ candidateRuns.set(sprintRun.id, sprintRun);
575
+ }
576
+ }
577
+ return [...candidateRuns.values()].sort((left, right) => (Date.parse(right.createdAt) - Date.parse(left.createdAt)))[0] || null;
578
+ }
579
+ resolveRehydratedDispatchStatus(taskRun) {
580
+ if (taskRun.state === "PENDING") {
581
+ return "queued";
582
+ }
583
+ if (taskRun.state === "PAUSED") {
584
+ return "paused";
585
+ }
586
+ return "running";
587
+ }
588
+ resolveTaskRunSessionKey(taskRun) {
589
+ const sessionId = taskRun.sessionId?.trim();
590
+ if (sessionId) {
591
+ return sessionId;
592
+ }
593
+ const sessionName = taskRun.sessionName?.trim();
594
+ if (!sessionName) {
595
+ return null;
596
+ }
597
+ return sessionName.replace(/^sessions\//, "");
598
+ }
429
599
  reconcileInterruptedTaskRuns() {
430
600
  const executionRepository = this.deps.executionRepository;
431
601
  if (typeof executionRepository.listTaskRunsByStates !== "function") {
@@ -123,6 +123,13 @@ export class SprintTaskDispatchService {
123
123
  state: "RUNNING",
124
124
  startedAt: queuedAt,
125
125
  });
126
+ if (julesClaim) {
127
+ this.executionRepository.associateProviderInvocationRuntime(julesClaim.id, {
128
+ sprintRunId: args.sprintRunId,
129
+ dispatchId: dispatch.id,
130
+ taskRunId: taskRun.id,
131
+ });
132
+ }
126
133
  this.executionRepository.appendTaskRunEvent(taskRun.id, "dispatch_started", "system", {
127
134
  dispatchId: dispatch.id,
128
135
  executorType,
@@ -4,12 +4,23 @@ This guide gets the MCP server and dashboard running locally with minimal setup.
4
4
 
5
5
  ## Prerequisites
6
6
 
7
- - Node.js 20+
8
- - pnpm
7
+ - Node.js 22+
8
+ - pnpm (only for building from source)
9
9
  - A valid Jules API key
10
10
  - Optional for remote git intelligence: GitHub CLI (`gh`) authenticated
11
11
 
12
- ## Install and Build
12
+ ## Install from npm (recommended)
13
+
14
+ Install the runtime globally and run the `codeux` command:
15
+
16
+ ```bash
17
+ npm i -g @codeuxai/codeux
18
+ codeux
19
+ ```
20
+
21
+ This starts the MCP server and dashboard. Skip ahead to [Configure API Key](#configure-api-key); the build steps below are only needed when running from source.
22
+
23
+ ## Install and Build (from source)
13
24
 
14
25
  ```bash
15
26
  pnpm install
@@ -120,6 +120,7 @@ Checks:
120
120
  - If auth is expected from host login state, is the relevant Docker auth mount enabled and is its mount path valid?
121
121
  - Docker mode requires daemon-visible workspace paths. Runtime now prefers repo-scoped worktree paths for Docker sessions.
122
122
  - Docker runtime state is stored under `~/.code-ux/runtime/docker/<repo-hash>/` by default (override with `JULES_DOCKER_RUNTIME_ROOT`). Cached setup image build contexts and build locks live under that root so setup-cache images survive dashboard restarts and concurrent post-restart jobs wait on the same build instead of starting duplicate builds.
123
+ - During Code UX restarts, Docker-backed provider invocations interrupted by a shutdown signal stay recoverable until startup confirms whether the labeled session container still exists.
123
124
  - Codex uses per-session container home directories under that runtime root to prevent stale state from previous Codex runs.
124
125
  - Runtime cleanup prunes stale `home-codex-*` session homes and stale shared runtime temp directories automatically once those sessions are no longer active.
125
126
  - Docker provider launches use readable container names such as `code-ux-codex-<session>` and mount provider arguments through a generated argv file instead of passing the full prompt through the host `docker run` command line. Packaged Windows Electron builds that fail with `spawn ENAMETOOLONG` during provider launch are using an older build or a non-provider launch path that still embeds a large payload in command arguments.
@@ -214,6 +215,7 @@ Transient provider failures are classified and managed in `src/shared/providers/
214
215
  - On startup, interrupted local CLI sessions (`cli-*` with `RUNNING`) are auto-recovered to `FAILED` so orchestration can safely retry them.
215
216
  - On startup, active `queued` and `running` sprint runs are resumed automatically in place; Code UX now restores the watch loop instead of requiring a manual sprint restart.
216
217
  - Local `docker_cli` task dispatches are rewritten to retryable failed state during that recovery, while durable Jules sessions and connected-worker dispatches remain attached to the resumed sprint run.
218
+ - If a restart finds active Jules task runs with persisted session ids but their sprint run was left `failed` or `cancelled` while the sprint is still active, startup recovery rehydrates a single sprint run, moves those durable Jules rows onto it, reopens their dispatch/provider runtime links, and resumes monitoring.
217
219
  - Failed CLI sessions can preserve their worktree for manual follow-up or assisted retry, based on CLI Workflow settings.
218
220
  - Dashboard task reruns now support a full clean reset:
219
221
  - the selected task always clears session, PR, merge, and intervention state before restart
@@ -77,7 +77,9 @@ Runtime resolution:
77
77
  - On startup, Code UX prunes stale Code UX Docker workspace volumes and orphaned helper/login containers. Cached setup-script images are content-addressed and are intentionally preserved across dashboard restarts so provider launches can reuse them until the base image, setup script content, or setup Dockerfile changes.
78
78
  - On startup, Code UX also performs automated database maintenance, pruning old completed task runs (and their cascaded child tables), VM activities, attention items, and realtime events according to the configured retention policy, followed by a `VACUUM` operation on database files to reclaim disk space.
79
79
  - restart recovery also treats interrupted Docker sessions without a live backing container as failed, so abandoned workspaces are reclaimed instead of waiting forever for a callback that cannot arrive.
80
+ - Docker provider runs interrupted by Code UX process shutdown keep their provider usage rows running when the spawner exits from a restart signal; startup recovery then resumes or fails them based on whether the labeled backing container still exists.
80
81
  - startup recovery now also requeues task-level CLI follow-up runs that were left in `in_progress` after QA/repair `Fix` work lost its backing container, so the orchestrator can start the container again instead of leaving the sprint stuck after a server restart.
82
+ - startup recovery treats Jules task sessions as durable remote runtime. If Code UX restarts after a sprint run or task dispatch was incorrectly terminalized while the sprint itself is still active, recovery rehydrates one sprint run, reattaches active Jules task runs/dispatches/provider invocation rows to it, and resumes the watch loop instead of failing the sessions.
81
83
  - When Code UX has to create a missing feature branch, it prefers `origin/<defaultBranch>` over the local `<defaultBranch>` ref when the remote-tracking base branch exists.
82
84
  - `main` is only the final fallback when no sprint, project, or system base branch is configured. Normal sprint and task flows use the resolved `git.defaultBranch` value from settings and project metadata.
83
85
  - the old global `/api/settings` contract is removed in favor of explicit scoped endpoints
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@codeuxai/codeux",
3
- "version": "0.8.1",
3
+ "version": "0.8.3",
4
4
  "description": "Containerized environment for running and managing AI sub-agents",
5
5
  "author": "Pierre Voss <p.voss@codeux.ai>",
6
6
  "license": "MIT",