@voybio/ace-swarm 0.2.5 → 2.4.1

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 (144) hide show
  1. package/CHANGELOG.md +19 -1
  2. package/README.md +21 -13
  3. package/assets/.agents/ACE/agent-qa/instructions.md +11 -0
  4. package/assets/agent-state/EVIDENCE_LOG.md +1 -1
  5. package/assets/agent-state/MODULES/roles/capability-framework.json +41 -0
  6. package/assets/agent-state/MODULES/roles/capability-git.json +33 -0
  7. package/assets/agent-state/MODULES/roles/capability-safety.json +37 -0
  8. package/assets/agent-state/MODULES/schemas/ACE_RUNTIME_PROFILE.schema.json +21 -0
  9. package/assets/agent-state/MODULES/schemas/RUNTIME_EXECUTOR_SESSION_REGISTRY.schema.json +43 -0
  10. package/assets/agent-state/MODULES/schemas/RUNTIME_TOOL_SPEC_REGISTRY.schema.json +43 -0
  11. package/assets/agent-state/MODULES/schemas/WORKSPACE_SESSION_REGISTRY.schema.json +11 -0
  12. package/assets/agent-state/STATUS.md +2 -2
  13. package/assets/agent-state/runtime-tool-specs.json +70 -2
  14. package/assets/instructions/ACE_Coder.instructions.md +13 -0
  15. package/assets/instructions/ACE_UI.instructions.md +11 -0
  16. package/assets/scripts/ace-hook-dispatch.mjs +70 -6
  17. package/assets/scripts/render-mcp-configs.sh +19 -5
  18. package/dist/ace-context.js +91 -11
  19. package/dist/ace-internal-tools.d.ts +3 -1
  20. package/dist/ace-internal-tools.js +10 -2
  21. package/dist/ace-server-instructions.js +3 -3
  22. package/dist/ace-state-resolver.js +5 -3
  23. package/dist/agent-runtime/role-adapters.d.ts +18 -1
  24. package/dist/agent-runtime/role-adapters.js +49 -5
  25. package/dist/astgrep-index.d.ts +57 -1
  26. package/dist/astgrep-index.js +140 -4
  27. package/dist/cli.js +232 -35
  28. package/dist/discovery-runtime-wrappers.d.ts +108 -0
  29. package/dist/discovery-runtime-wrappers.js +615 -0
  30. package/dist/handoff-registry.js +5 -5
  31. package/dist/helpers/artifacts.d.ts +19 -0
  32. package/dist/helpers/artifacts.js +152 -0
  33. package/dist/helpers/bootstrap.d.ts +24 -0
  34. package/dist/helpers/bootstrap.js +894 -0
  35. package/dist/helpers/constants.d.ts +53 -0
  36. package/dist/helpers/constants.js +295 -0
  37. package/dist/helpers/drift.d.ts +13 -0
  38. package/dist/helpers/drift.js +45 -0
  39. package/dist/helpers/path-utils.d.ts +24 -0
  40. package/dist/helpers/path-utils.js +123 -0
  41. package/dist/helpers/store-resolution.d.ts +19 -0
  42. package/dist/helpers/store-resolution.js +305 -0
  43. package/dist/helpers/workspace-root.d.ts +3 -0
  44. package/dist/helpers/workspace-root.js +80 -0
  45. package/dist/helpers.d.ts +8 -125
  46. package/dist/helpers.js +8 -1768
  47. package/dist/job-scheduler.js +33 -7
  48. package/dist/json-sanitizer.d.ts +16 -0
  49. package/dist/json-sanitizer.js +26 -0
  50. package/dist/local-model-policy.d.ts +27 -0
  51. package/dist/local-model-policy.js +84 -0
  52. package/dist/local-model-runtime.d.ts +6 -0
  53. package/dist/local-model-runtime.js +33 -21
  54. package/dist/model-bridge.d.ts +13 -1
  55. package/dist/model-bridge.js +410 -23
  56. package/dist/orchestrator-supervisor.d.ts +56 -0
  57. package/dist/orchestrator-supervisor.js +179 -1
  58. package/dist/plan-proposal.d.ts +115 -0
  59. package/dist/plan-proposal.js +1073 -0
  60. package/dist/run-ledger.js +3 -3
  61. package/dist/runtime-command.d.ts +8 -0
  62. package/dist/runtime-command.js +38 -6
  63. package/dist/runtime-executor.d.ts +20 -1
  64. package/dist/runtime-executor.js +737 -172
  65. package/dist/runtime-profile.d.ts +32 -0
  66. package/dist/runtime-profile.js +89 -13
  67. package/dist/runtime-tool-specs.d.ts +39 -0
  68. package/dist/runtime-tool-specs.js +144 -28
  69. package/dist/safe-edit.d.ts +7 -0
  70. package/dist/safe-edit.js +163 -37
  71. package/dist/schemas.js +48 -1
  72. package/dist/server.js +51 -0
  73. package/dist/shared.d.ts +3 -2
  74. package/dist/shared.js +2 -0
  75. package/dist/status-events.js +9 -6
  76. package/dist/store/ace-packed-store.d.ts +3 -2
  77. package/dist/store/ace-packed-store.js +188 -110
  78. package/dist/store/bootstrap-store.d.ts +2 -1
  79. package/dist/store/bootstrap-store.js +102 -83
  80. package/dist/store/cache-workspace.js +11 -5
  81. package/dist/store/materializers/context-snapshot-materializer.js +6 -2
  82. package/dist/store/materializers/hook-context-materializer.d.ts +6 -9
  83. package/dist/store/materializers/hook-context-materializer.js +11 -21
  84. package/dist/store/materializers/host-file-materializer.js +6 -0
  85. package/dist/store/materializers/projection-manager.d.ts +0 -1
  86. package/dist/store/materializers/projection-manager.js +5 -13
  87. package/dist/store/materializers/scheduler-projection-materializer.js +1 -1
  88. package/dist/store/materializers/vericify-projector.d.ts +7 -7
  89. package/dist/store/materializers/vericify-projector.js +11 -11
  90. package/dist/store/repositories/local-model-runtime-repository.d.ts +120 -3
  91. package/dist/store/repositories/local-model-runtime-repository.js +242 -6
  92. package/dist/store/repositories/vericify-repository.d.ts +1 -1
  93. package/dist/store/skills-install.d.ts +4 -0
  94. package/dist/store/skills-install.js +21 -12
  95. package/dist/store/state-reader.d.ts +2 -0
  96. package/dist/store/state-reader.js +20 -0
  97. package/dist/store/store-artifacts.d.ts +7 -0
  98. package/dist/store/store-artifacts.js +27 -1
  99. package/dist/store/store-authority-audit.d.ts +18 -1
  100. package/dist/store/store-authority-audit.js +115 -5
  101. package/dist/store/store-snapshot.d.ts +3 -0
  102. package/dist/store/store-snapshot.js +22 -2
  103. package/dist/store/workspace-store-paths.d.ts +39 -0
  104. package/dist/store/workspace-store-paths.js +94 -0
  105. package/dist/store/write-coordinator.d.ts +65 -0
  106. package/dist/store/write-coordinator.js +386 -0
  107. package/dist/todo-state.js +5 -5
  108. package/dist/tools-agent.d.ts +20 -0
  109. package/dist/tools-agent.js +789 -25
  110. package/dist/tools-discovery.js +136 -1
  111. package/dist/tools-files.d.ts +7 -0
  112. package/dist/tools-files.js +1002 -11
  113. package/dist/tools-framework.js +105 -66
  114. package/dist/tools-handoff.js +2 -2
  115. package/dist/tools-lifecycle.js +4 -4
  116. package/dist/tools-memory.js +6 -6
  117. package/dist/tools-todo.js +2 -2
  118. package/dist/tracker-adapters.d.ts +1 -1
  119. package/dist/tracker-adapters.js +13 -18
  120. package/dist/tracker-sync.js +5 -3
  121. package/dist/tui/agent-runner.js +3 -1
  122. package/dist/tui/chat.js +103 -7
  123. package/dist/tui/dashboard.d.ts +1 -0
  124. package/dist/tui/dashboard.js +43 -0
  125. package/dist/tui/index.js +10 -1
  126. package/dist/tui/layout.d.ts +20 -0
  127. package/dist/tui/layout.js +31 -1
  128. package/dist/tui/local-model-contract.d.ts +6 -2
  129. package/dist/tui/local-model-contract.js +16 -3
  130. package/dist/tui/ollama.d.ts +8 -1
  131. package/dist/tui/ollama.js +53 -12
  132. package/dist/tui/openai-compatible.d.ts +13 -0
  133. package/dist/tui/openai-compatible.js +305 -5
  134. package/dist/tui/provider-discovery.d.ts +1 -0
  135. package/dist/tui/provider-discovery.js +35 -11
  136. package/dist/vericify-bridge.d.ts +6 -1
  137. package/dist/vericify-bridge.js +27 -3
  138. package/dist/workspace-manager.d.ts +30 -3
  139. package/dist/workspace-manager.js +257 -27
  140. package/package.json +1 -2
  141. package/dist/internal-tool-runtime.d.ts +0 -21
  142. package/dist/internal-tool-runtime.js +0 -136
  143. package/dist/store/workspace-snapshot.d.ts +0 -26
  144. package/dist/store/workspace-snapshot.js +0 -107
package/dist/tui/chat.js CHANGED
@@ -380,7 +380,11 @@ export class ChatSession extends EventEmitter {
380
380
  return;
381
381
  const { updated_at: _updatedAt, ...record } = status;
382
382
  void withLocalModelRuntimeRepository(this.workspaceRoot, async (repo) => {
383
- await repo.upsertRuntimeStatus(record);
383
+ await repo.upsertRuntimeStatusWithTransition(record, {
384
+ from: this.currentRuntimeStatus?.bridge_status,
385
+ reason: "Interactive runtime status updated after provider/tool progress.",
386
+ evidence_refs: [this.sessionId],
387
+ });
384
388
  }).catch((error) => {
385
389
  this.emit("error", `Runtime status persistence failed: ${formatErrorMessage(error)}`);
386
390
  });
@@ -390,14 +394,26 @@ export class ChatSession extends EventEmitter {
390
394
  return;
391
395
  void withLocalModelRuntimeRepository(this.workspaceRoot, async (repo) => {
392
396
  if (input.activationLedger) {
393
- await repo.upsertActivationLedger(input.activationLedger);
397
+ await repo.upsertActivationLedgerWithTransition(input.activationLedger, {
398
+ from: this.activationLedger ? "updated" : "created",
399
+ reason: "Interactive activation ledger updated after ACE session progress.",
400
+ evidence_refs: [this.sessionId],
401
+ });
394
402
  }
395
403
  if (input.runtimeStatus) {
396
404
  const { updated_at: _updatedAt, ...statusRecord } = input.runtimeStatus;
397
- await repo.upsertRuntimeStatus(statusRecord);
405
+ await repo.upsertRuntimeStatusWithTransition(statusRecord, {
406
+ from: this.currentRuntimeStatus?.bridge_status,
407
+ reason: "Interactive runtime status updated after ACE session progress.",
408
+ evidence_refs: [this.sessionId],
409
+ });
398
410
  }
399
411
  if (input.continuity) {
400
- await repo.upsertContinuityRecord(input.continuity);
412
+ await repo.upsertContinuityRecordWithTransition(input.continuity, {
413
+ from: this.currentRuntimeStatus?.bridge_status,
414
+ reason: "Interactive continuity record updated after ACE session progress.",
415
+ evidence_refs: [this.sessionId],
416
+ });
401
417
  }
402
418
  }).catch((error) => {
403
419
  this.emit("error", `ACE runtime persistence failed: ${formatErrorMessage(error)}`);
@@ -416,15 +432,79 @@ export class ChatSession extends EventEmitter {
416
432
  preferredRole: this.aceRole,
417
433
  });
418
434
  const executionRole = preflight.recommended_role ?? this.aceRole;
435
+ const prevLedger = this.activationLedger;
419
436
  let activationLedger = nextActivationLedger(this.sessionId, this.activationLedger ?? undefined, preflight.recommended_next_action);
420
- const startupNudge = buildStartupNudge(preflight, activationLedger);
437
+ // Detect previously shown but unaccepted/unauto-executed nudges as ignored
438
+ if (prevLedger) {
439
+ const autoExecuted = prevLedger.auto_executed_nudges ?? [];
440
+ const accepted = prevLedger.accepted_nudges;
441
+ const alreadyIgnored = prevLedger.ignored_nudges ?? [];
442
+ const ignoredNow = prevLedger.shown_nudges.filter((id) => !accepted.includes(id) && !autoExecuted.includes(id) && !alreadyIgnored.includes(id));
443
+ if (ignoredNow.length > 0) {
444
+ activationLedger = {
445
+ ...activationLedger,
446
+ ignored_nudges: uniqueStrings([...(activationLedger.ignored_nudges ?? []), ...ignoredNow]),
447
+ };
448
+ if (this.workspaceRoot) {
449
+ const wsr = this.workspaceRoot;
450
+ for (const nudgeId of ignoredNow) {
451
+ void withLocalModelRuntimeRepository(wsr, async (repo) => {
452
+ await repo.appendTransitionRecord({
453
+ session_id: this.sessionId,
454
+ subject_kind: "nudge",
455
+ subject_id: nudgeId,
456
+ from: "shown",
457
+ to: "ignored",
458
+ reason: `Nudge ${nudgeId} ignored: user sent new message without accepting`,
459
+ evidence_refs: [],
460
+ });
461
+ }).catch(() => { });
462
+ }
463
+ }
464
+ }
465
+ }
466
+ const startupNudge = buildStartupNudge(preflight, activationLedger, this.currentRuntimeStatus);
421
467
  if (startupNudge) {
422
468
  activationLedger = {
423
469
  ...activationLedger,
424
470
  shown_nudges: uniqueStrings([...activationLedger.shown_nudges, startupNudge.id]),
471
+ ...(startupNudge.auto_executable
472
+ ? {
473
+ auto_executed_nudges: uniqueStrings([
474
+ ...(activationLedger.auto_executed_nudges ?? []),
475
+ startupNudge.id,
476
+ ]),
477
+ }
478
+ : {}),
425
479
  };
426
480
  this.pushSystemMessage(`Hint: ${startupNudge.text}`);
427
481
  this.emit("startup_nudge", startupNudge);
482
+ if (this.workspaceRoot) {
483
+ const wsr = this.workspaceRoot;
484
+ const nudge = startupNudge;
485
+ void withLocalModelRuntimeRepository(wsr, async (repo) => {
486
+ await repo.appendTransitionRecord({
487
+ session_id: this.sessionId,
488
+ subject_kind: "nudge",
489
+ subject_id: nudge.id,
490
+ from: "new",
491
+ to: "shown",
492
+ reason: `Startup nudge shown for action: ${nudge.recommended_action}`,
493
+ evidence_refs: [],
494
+ });
495
+ if (nudge.auto_executable) {
496
+ await repo.appendTransitionRecord({
497
+ session_id: this.sessionId,
498
+ subject_kind: "nudge",
499
+ subject_id: nudge.id,
500
+ from: "shown",
501
+ to: "auto_executed",
502
+ reason: `Auto-executed safe action: ${nudge.recommended_action}`,
503
+ evidence_refs: [],
504
+ });
505
+ }
506
+ }).catch(() => { });
507
+ }
428
508
  }
429
509
  let runtimeStatus = this.buildRuntimeStatusPacket({
430
510
  role: executionRole,
@@ -488,15 +568,31 @@ export class ChatSession extends EventEmitter {
488
568
  },
489
569
  onToolCall: (tool, args) => {
490
570
  toolNames.push(tool);
571
+ const nudgeToAccept = tool === startupNudge?.recommended_action ? startupNudge : undefined;
491
572
  activationLedger = {
492
573
  ...activationLedger,
493
574
  updated_at: Date.now(),
494
575
  activated_tools: uniqueStrings([...activationLedger.activated_tools, tool]),
495
576
  activated_roles: uniqueStrings([...activationLedger.activated_roles, executionRole]),
496
- accepted_nudges: tool === startupNudge?.recommended_action
497
- ? uniqueStrings([...activationLedger.accepted_nudges, startupNudge.id])
577
+ accepted_nudges: nudgeToAccept
578
+ ? uniqueStrings([...activationLedger.accepted_nudges, nudgeToAccept.id])
498
579
  : activationLedger.accepted_nudges,
499
580
  };
581
+ if (nudgeToAccept && this.workspaceRoot) {
582
+ const wsr = this.workspaceRoot;
583
+ const nudge = nudgeToAccept;
584
+ void withLocalModelRuntimeRepository(wsr, async (repo) => {
585
+ await repo.appendTransitionRecord({
586
+ session_id: this.sessionId,
587
+ subject_kind: "nudge",
588
+ subject_id: nudge.id,
589
+ from: "shown",
590
+ to: "manually_accepted",
591
+ reason: `Nudge accepted via tool call: ${tool}`,
592
+ evidence_refs: [],
593
+ });
594
+ }).catch(() => { });
595
+ }
500
596
  runtimeStatus = {
501
597
  ...runtimeStatus,
502
598
  bridge_status: "running",
@@ -18,6 +18,7 @@ export declare class DashboardState extends EventEmitter {
18
18
  private runtimeEvents;
19
19
  private phase;
20
20
  private latestRuntimeStatus;
21
+ private latestTransitions;
21
22
  constructor(workspaceRoot: string);
22
23
  startWatching(): void;
23
24
  stopWatching(): void;
@@ -7,6 +7,7 @@
7
7
  import { EventEmitter } from "node:events";
8
8
  import { pollStore } from "../store/state-reader.js";
9
9
  import { getWorkspaceStorePath } from "../store/store-snapshot.js";
10
+ import { getLivenessDefaults } from "../runtime-profile.js";
10
11
  function dedupeEvents(events) {
11
12
  const seen = new Set();
12
13
  const ordered = [];
@@ -35,6 +36,18 @@ export function selectLiveRuntimeStatus(statuses) {
35
36
  }
36
37
  function derivePhase(snapshot) {
37
38
  const runtimeStatus = selectLiveRuntimeStatus(snapshot.runtimeStatuses);
39
+ if (runtimeStatus?.sleep_state === "stalled") {
40
+ return "Stalled";
41
+ }
42
+ if (runtimeStatus?.sleep_state === "sleeping") {
43
+ return "Sleeping";
44
+ }
45
+ if (runtimeStatus?.sleep_state === "approval_pending") {
46
+ return "Approval Pending";
47
+ }
48
+ if (runtimeStatus?.sleep_state === "dependency_wait") {
49
+ return runtimeStatus.waiting_on ? `Waiting: ${runtimeStatus.waiting_on}` : "Waiting";
50
+ }
38
51
  if (runtimeStatus?.bridge_status === "blocked") {
39
52
  return "Blocked";
40
53
  }
@@ -48,6 +61,15 @@ function derivePhase(snapshot) {
48
61
  return "Retrying";
49
62
  }
50
63
  if (runtimeStatus?.bridge_status === "running") {
64
+ const kind = runtimeStatus.surface_kind;
65
+ if (kind === "unattended_runtime")
66
+ return "Running (Unattended)";
67
+ if (kind === "scheduled_job")
68
+ return "Running (Scheduled)";
69
+ if (kind === "bridge_resumed")
70
+ return "Running (Resumed)";
71
+ if (kind === "supervised_step")
72
+ return "Running (Supervised)";
51
73
  return "Running";
52
74
  }
53
75
  if (snapshot.jobs.some((job) => String(job.status ?? "") === "running")) {
@@ -73,6 +95,7 @@ export class DashboardState extends EventEmitter {
73
95
  runtimeEvents = [];
74
96
  phase = "Initializing";
75
97
  latestRuntimeStatus;
98
+ latestTransitions = [];
76
99
  constructor(workspaceRoot) {
77
100
  super();
78
101
  this.workspaceRoot = workspaceRoot;
@@ -108,6 +131,7 @@ export class DashboardState extends EventEmitter {
108
131
  runtimeStatus: this.latestRuntimeStatus,
109
132
  tasks: this.tasks,
110
133
  events: dedupeEvents([...this.persistedEvents, ...this.runtimeEvents]).slice(-50),
134
+ recentTransitions: this.latestTransitions,
111
135
  };
112
136
  }
113
137
  getLatestRuntimeStatus() {
@@ -141,6 +165,25 @@ export class DashboardState extends EventEmitter {
141
165
  this.persistedEvents = dedupeEvents([...trackerEvents, ...ledgerEvents]).slice(-300);
142
166
  this.phase = derivePhase(snapshot);
143
167
  this.latestRuntimeStatus = selectLiveRuntimeStatus(snapshot.runtimeStatuses);
168
+ // Compute read-only staleness projection — no watcher, computed on each snapshot
169
+ if (this.latestRuntimeStatus?.last_progress_at) {
170
+ const livenessDefaults = getLivenessDefaults(this.latestRuntimeStatus.model_class);
171
+ const effectiveStallWindowMs = this.latestRuntimeStatus.stall_window_ms ?? livenessDefaults.stall_window_ms;
172
+ const stalenessSec = (Date.now() - this.latestRuntimeStatus.last_progress_at) / 1000;
173
+ const stallWindowSec = effectiveStallWindowMs / 1000;
174
+ this.latestRuntimeStatus["staleness_seconds"] = stalenessSec;
175
+ this.latestRuntimeStatus["stall_window_seconds"] = stallWindowSec;
176
+ this.latestRuntimeStatus["is_stale"] = stalenessSec >= stallWindowSec;
177
+ }
178
+ this.latestTransitions = (snapshot.recentTransitions ?? []).map((t) => ({
179
+ subject_kind: t.subject_kind,
180
+ subject_id: t.subject_id,
181
+ from: t.from,
182
+ to: t.to,
183
+ reason: t.reason,
184
+ evidence_refs: t.evidence_refs,
185
+ at: t.at,
186
+ }));
144
187
  // Surface discovered providers to the TUI controller so dashboard controls
145
188
  // reflect what's actually available in the store.
146
189
  if (snapshot.providers.length > 0) {
package/dist/tui/index.js CHANGED
@@ -56,7 +56,13 @@ export class AceTui {
56
56
  const workspaceRoot = options.workspaceRoot ?? WORKSPACE_ROOT;
57
57
  this.workspaceRoot = workspaceRoot;
58
58
  this.provider = this.normalizeProvider(options.provider ?? inferProviderFromModel(options.model) ?? "ollama") ?? "ollama";
59
- this.model = (options.model ?? defaultModelForProvider(this.provider)).trim();
59
+ const initialModel = options.model ??
60
+ (this.provider === "ollama"
61
+ ? defaultModelForProvider(this.provider)
62
+ : this.provider === "llama.cpp"
63
+ ? ""
64
+ : defaultModelForProvider(this.provider));
65
+ this.model = initialModel.trim();
60
66
  // Initialize modules
61
67
  const colorLevel = detectColorLevel();
62
68
  for (const [provider, baseUrl] of Object.entries(options.providerBaseUrls ?? {})) {
@@ -757,6 +763,9 @@ export class AceTui {
757
763
  else if (this.provider === "ollama") {
758
764
  this.model = DEFAULT_OLLAMA_MODEL;
759
765
  }
766
+ else if (this.provider === "llama.cpp") {
767
+ this.model = "";
768
+ }
760
769
  else {
761
770
  this.model = defaultModelForProvider(this.provider);
762
771
  }
@@ -60,11 +60,20 @@ interface RuntimeStatusSummary {
60
60
  bridge_status: string;
61
61
  preflight_state: string;
62
62
  role?: string;
63
+ model_class?: "frontier" | "mid" | "small_local";
63
64
  approval_state?: string;
64
65
  active_tool?: string;
65
66
  active_tool_role?: string;
66
67
  recommended_next_action?: string;
67
68
  blocked_reason?: string;
69
+ active_workspace_path?: string;
70
+ workspace_hook_health?: "ok" | "degraded" | "failed";
71
+ staleness_seconds?: number;
72
+ stall_window_seconds?: number;
73
+ is_stale?: boolean;
74
+ stall_restart_count?: number;
75
+ current_task?: string;
76
+ surface_kind?: string;
68
77
  }
69
78
  export declare class LayoutManager {
70
79
  private zones;
@@ -88,6 +97,7 @@ export declare class LayoutManager {
88
97
  renderStatusBar(state: StatusBarState): void;
89
98
  renderInputLine(buffer: string, cursorPos: number, mode: string, prompt?: string): void;
90
99
  renderDashboard(data: DashboardData, controls?: DashboardControlState): void;
100
+ private renderTransitionsPanel;
91
101
  private renderStatusPanel;
92
102
  private renderControlStrip;
93
103
  private renderTasksPanel;
@@ -119,11 +129,21 @@ export interface DashboardData {
119
129
  runtimeStatus?: RuntimeStatusSummary;
120
130
  tasks: TaskItem[];
121
131
  events: ActivityEvent[];
132
+ recentTransitions?: Array<{
133
+ subject_kind: string;
134
+ subject_id: string;
135
+ from?: string;
136
+ to: string;
137
+ reason: string;
138
+ evidence_refs?: string[];
139
+ at: number;
140
+ }>;
122
141
  }
123
142
  export interface TaskItem {
124
143
  title: string;
125
144
  done: boolean;
126
145
  priority?: string;
146
+ upstream_annotation?: string;
127
147
  }
128
148
  export interface ActivityEvent {
129
149
  timestamp: number;
@@ -250,6 +250,18 @@ export class LayoutManager {
250
250
  drawBox(content.x, actY, content.width, actH, { title: "Agent Activity", titleColor: fg.brightGreen, borderColor: fg.gray, rounded: true });
251
251
  this.renderActivityLog(content.x + 2, actY + 1, content.width - 4, actH - 2, data.events);
252
252
  }
253
+ renderTransitionsPanel(x, y, w, data) {
254
+ const transitions = data.recentTransitions?.slice(-3) ?? [];
255
+ if (transitions.length === 0)
256
+ return;
257
+ writeAt(x, y, `${fg.gray}─── Recent Transitions ───`);
258
+ for (let i = 0; i < transitions.length; i++) {
259
+ const t = transitions[i];
260
+ const age = Math.round((Date.now() - t.at) / 1000);
261
+ const line = `${fg.gray}${age}s ago ${fg.cyan}${t.subject_kind}${fg.gray}:${fg.white}${t.to}${fg.gray} — ${truncate(t.reason, 28)}`;
262
+ writeAt(x, y + 1 + i, truncate(line + style.reset, w));
263
+ }
264
+ }
253
265
  renderStatusPanel(x, y, w, h, data) {
254
266
  const lines = [
255
267
  `${fg.gray}Phase: ${fg.brightWhite}${data.phase}`,
@@ -266,12 +278,29 @@ export class LayoutManager {
266
278
  data.runtimeStatus?.role
267
279
  ? `${fg.gray}Role: ${fg.brightCyan}${data.runtimeStatus.role}`
268
280
  : undefined,
281
+ data.runtimeStatus?.model_class
282
+ ? `${fg.gray}Model: ${fg.brightCyan}${data.runtimeStatus.model_class}`
283
+ : undefined,
269
284
  data.runtimeStatus?.blocked_reason
270
285
  ? `${fg.gray}Blocker: ${fg.brightRed}${data.runtimeStatus.blocked_reason}`
271
286
  : undefined,
272
287
  data.runtimeStatus?.recommended_next_action
273
288
  ? `${fg.gray}Next: ${fg.white}${data.runtimeStatus.recommended_next_action}`
274
289
  : undefined,
290
+ data.runtimeStatus?.active_workspace_path
291
+ ? `${fg.gray}Wrkspace:${fg.cyan} ${truncate(data.runtimeStatus.active_workspace_path, 32)} ${fg.gray}[hook: ${data.runtimeStatus.workspace_hook_health ?? "?"}]`
292
+ : undefined,
293
+ data.runtimeStatus?.stall_restart_count
294
+ ? `${fg.gray}Stalls: ${fg.yellow}${data.runtimeStatus.stall_restart_count} restart(s)`
295
+ : undefined,
296
+ data.runtimeStatus?.is_stale
297
+ ? `${fg.brightRed}⚠ STALE ${fg.gray}${(data.runtimeStatus.staleness_seconds ?? 0).toFixed(0)}s since last progress (window: ${(data.runtimeStatus.stall_window_seconds ?? 0).toFixed(0)}s)`
298
+ : data.runtimeStatus?.staleness_seconds !== undefined
299
+ ? `${fg.gray}Progress:${fg.brightGreen} ${(data.runtimeStatus.staleness_seconds).toFixed(0)}s ago`
300
+ : undefined,
301
+ data.runtimeStatus?.current_task
302
+ ? `${fg.gray}Task: ${fg.white}${truncate(data.runtimeStatus.current_task, 40)}`
303
+ : undefined,
275
304
  `${fg.gray}Provider:${fg.brightCyan} ${padRight(data.provider, 8)}`,
276
305
  `${fg.gray}Model: ${fg.brightCyan}${data.model}`,
277
306
  `${fg.gray}Agents: ${fg.brightGreen}${data.activeAgents}${fg.gray}/${data.totalAgents} active`,
@@ -304,7 +333,8 @@ export class LayoutManager {
304
333
  const t = tasks[i];
305
334
  const icon = t.done ? `${fg.green}${symbols.check}` : `${fg.gray}${symbols.hollowBullet}`;
306
335
  const priority = t.priority ? `${fg.gray}[${t.priority}]` : "";
307
- const line = `${icon} ${fg.white}${t.title} ${priority}${style.reset}`;
336
+ const upstream = t.upstream_annotation ? ` ${fg.gray}${t.upstream_annotation}` : "";
337
+ const line = `${icon} ${fg.white}${t.title} ${priority}${upstream}${style.reset}`;
308
338
  writeAt(x, y + i, truncate(line, w));
309
339
  }
310
340
  if (tasks.length === 0) {
@@ -15,11 +15,14 @@ export interface AcePreflightPacket {
15
15
  recall_summary?: string;
16
16
  should_synthesize_plan: boolean;
17
17
  }
18
- export interface StartupNudge {
18
+ export interface AceStartupNudge {
19
19
  id: string;
20
20
  text: string;
21
21
  recommended_action: string;
22
+ auto_executable: boolean;
22
23
  }
24
+ /** @deprecated Use AceStartupNudge */
25
+ export type StartupNudge = AceStartupNudge;
23
26
  export declare function shouldSynthesizeShortPlan(task: string): boolean;
24
27
  export declare function buildAcePreflightPacket(input: {
25
28
  sessionId: string;
@@ -28,7 +31,8 @@ export declare function buildAcePreflightPacket(input: {
28
31
  preferredRole?: string;
29
32
  }): AcePreflightPacket;
30
33
  export declare function nextActivationLedger(sessionId: string, current: AceSessionActivationLedger | undefined, recommendedAction: string | undefined): AceSessionActivationLedger;
31
- export declare function buildStartupNudge(preflight: AcePreflightPacket, ledger: AceSessionActivationLedger): StartupNudge | undefined;
34
+ export declare function shouldAutoExecuteNudge(action: string, surfaceKind: AceRuntimeStatusPacket["surface_kind"] | undefined): boolean;
35
+ export declare function buildStartupNudge(preflight: AcePreflightPacket, ledger: AceSessionActivationLedger, statusPacket?: AceRuntimeStatusPacket | null): AceStartupNudge | undefined;
32
36
  export declare function buildBridgeTaskInput(conversation: string, preflight: AcePreflightPacket): string;
33
37
  export declare function mapBridgeResultToRuntimeStatus(input: {
34
38
  current: AceRuntimeStatusPacket;
@@ -122,10 +122,18 @@ export function nextActivationLedger(sessionId, current, recommendedAction) {
122
122
  accepted_nudges: [...(current?.accepted_nudges ?? [])],
123
123
  activated_tools: [...(current?.activated_tools ?? [])],
124
124
  activated_roles: [...(current?.activated_roles ?? [])],
125
- last_recommended_action: recommendedAction ?? current?.last_recommended_action,
125
+ auto_executed_nudges: current?.auto_executed_nudges ? [...current.auto_executed_nudges] : undefined,
126
+ ignored_nudges: current?.ignored_nudges ? [...current.ignored_nudges] : undefined,
126
127
  };
127
128
  }
128
- export function buildStartupNudge(preflight, ledger) {
129
+ export function shouldAutoExecuteNudge(action, surfaceKind) {
130
+ const SAFE_TRIO = new Set(["validate_framework", "recall_context", "run_orchestrator"]);
131
+ return (SAFE_TRIO.has(action) &&
132
+ (surfaceKind === "unattended_runtime" ||
133
+ surfaceKind === "scheduled_job" ||
134
+ surfaceKind === "supervised_step"));
135
+ }
136
+ export function buildStartupNudge(preflight, ledger, statusPacket) {
129
137
  const action = preflight.recommended_next_action;
130
138
  if (!action)
131
139
  return undefined;
@@ -140,7 +148,12 @@ export function buildStartupNudge(preflight, ledger) {
140
148
  : action === "route_task"
141
149
  ? "Role selection is still ambiguous. Route the task before deeper work."
142
150
  : "Load ACE context first with recall_context before deeper work.";
143
- return { id: nudgeId, text, recommended_action: action };
151
+ return {
152
+ id: nudgeId,
153
+ text,
154
+ recommended_action: action,
155
+ auto_executable: shouldAutoExecuteNudge(action, statusPacket?.surface_kind),
156
+ };
144
157
  }
145
158
  function compactTrapPack() {
146
159
  return [
@@ -111,6 +111,13 @@ export declare class OllamaClient {
111
111
  export declare class OllamaError extends Error {
112
112
  statusCode: number;
113
113
  responseBody: string;
114
- constructor(message: string, statusCode: number, responseBody: string);
114
+ kind?: string;
115
+ meta?: Record<string, unknown>;
116
+ suggested_remediation?: string;
117
+ constructor(message: string, statusCode: number, responseBody: string, options?: {
118
+ kind?: string;
119
+ model?: string;
120
+ suggested_remediation?: string;
121
+ });
115
122
  }
116
123
  //# sourceMappingURL=ollama.d.ts.map
@@ -5,6 +5,24 @@
5
5
  * Zero dependencies — uses Node.js built-in fetch/http.
6
6
  * Supports model listing, pulling, chat streaming, and health checks.
7
7
  */
8
+ function sleep(ms) {
9
+ return new Promise((resolve) => setTimeout(resolve, ms));
10
+ }
11
+ function requestedModelFromOptions(options) {
12
+ if (typeof options?.body !== "string")
13
+ return undefined;
14
+ try {
15
+ const parsed = JSON.parse(options.body);
16
+ return typeof parsed.model === "string"
17
+ ? parsed.model
18
+ : typeof parsed.name === "string"
19
+ ? parsed.name
20
+ : undefined;
21
+ }
22
+ catch {
23
+ return undefined;
24
+ }
25
+ }
8
26
  // ── Client ───────────────────────────────────────────────────────────
9
27
  export class OllamaClient {
10
28
  baseUrl;
@@ -124,19 +142,36 @@ export class OllamaClient {
124
142
  throw new OllamaError("Ollama base URL is not configured. Set one explicitly or run `ace doctor --scan`.", 0, "");
125
143
  }
126
144
  const url = `${this.baseUrl}${path}`;
127
- const res = await fetch(url, {
128
- ...options,
129
- headers: {
130
- "Content-Type": "application/json",
131
- Accept: "application/json",
132
- ...(options?.headers ?? {}),
133
- },
134
- });
135
- if (!res.ok) {
145
+ const maxAttempts = 3;
146
+ let lastError;
147
+ for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
148
+ const res = await fetch(url, {
149
+ ...options,
150
+ headers: {
151
+ "Content-Type": "application/json",
152
+ Accept: "application/json",
153
+ ...(options?.headers ?? {}),
154
+ },
155
+ });
156
+ if (res.ok)
157
+ return res;
136
158
  const text = await res.text().catch(() => "");
137
- throw new OllamaError(`Ollama API error: ${res.status} ${res.statusText}`, res.status, text);
159
+ const requestedModel = requestedModelFromOptions(options);
160
+ if (res.status >= 500 && /unable to load model/i.test(text)) {
161
+ throw new OllamaError(`Ollama was unable to load model${requestedModel ? ` '${requestedModel}'` : ""}. ` +
162
+ `Run \`ollama pull ${requestedModel ?? "<model>"}\` or \`ace doctor --repair-ollama\`.`, res.status, text, {
163
+ kind: "ollama_model_load_error",
164
+ model: requestedModel,
165
+ suggested_remediation: `run ollama pull ${requestedModel ?? "<model>"} or ace doctor --repair-ollama`,
166
+ });
167
+ }
168
+ lastError = new OllamaError(`Ollama API error: ${res.status} ${res.statusText}`, res.status, text);
169
+ if (res.status < 500 || attempt === maxAttempts) {
170
+ throw lastError;
171
+ }
172
+ await sleep(100 * attempt);
138
173
  }
139
- return res;
174
+ throw lastError ?? new OllamaError("Ollama API request failed", 0, "");
140
175
  }
141
176
  /** Stream newline-delimited JSON from a ReadableStream */
142
177
  async *streamJsonLines(body) {
@@ -182,11 +217,17 @@ export class OllamaClient {
182
217
  export class OllamaError extends Error {
183
218
  statusCode;
184
219
  responseBody;
185
- constructor(message, statusCode, responseBody) {
220
+ kind;
221
+ meta;
222
+ suggested_remediation;
223
+ constructor(message, statusCode, responseBody, options) {
186
224
  super(message);
187
225
  this.statusCode = statusCode;
188
226
  this.responseBody = responseBody;
189
227
  this.name = "OllamaError";
228
+ this.kind = options?.kind;
229
+ this.meta = options?.model ? { model: options.model } : undefined;
230
+ this.suggested_remediation = options?.suggested_remediation;
190
231
  }
191
232
  }
192
233
  //# sourceMappingURL=ollama.js.map
@@ -15,6 +15,7 @@ export interface OpenAICompatibleChatRequest {
15
15
  messages: OpenAICompatibleChatMessage[];
16
16
  temperature?: number;
17
17
  topP?: number;
18
+ onProviderEvent?: (event: OpenAICompatibleProviderEvent) => void;
18
19
  }
19
20
  export interface OpenAICompatibleChatChunk {
20
21
  text: string;
@@ -22,6 +23,18 @@ export interface OpenAICompatibleChatChunk {
22
23
  promptTokens?: number;
23
24
  completionTokens?: number;
24
25
  }
26
+ export type OpenAICompatibleAdapterStage = "streaming_chat" | "non_streaming_chat" | "completions";
27
+ export interface OpenAICompatibleProviderEvent {
28
+ provider: string;
29
+ stage: OpenAICompatibleAdapterStage;
30
+ event: "attempt" | "success" | "fallback" | "parse_error";
31
+ reason?: string;
32
+ statusCode?: number;
33
+ next_stage?: OpenAICompatibleAdapterStage;
34
+ fallback_count?: number;
35
+ sample_hex?: string;
36
+ sample_text?: string;
37
+ }
25
38
  export interface ProviderConfigOverride {
26
39
  baseUrl?: string;
27
40
  apiKey?: string;