agent-relay-server 0.28.0 → 0.29.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-relay-server",
3
- "version": "0.28.0",
3
+ "version": "0.29.0",
4
4
  "description": "Lightweight HTTP message relay for inter-agent communication across machines",
5
5
  "module": "src/index.ts",
6
6
  "type": "module",
@@ -33,7 +33,7 @@
33
33
  "CONTRIBUTING.md"
34
34
  ],
35
35
  "dependencies": {
36
- "agent-relay-sdk": "0.2.17"
36
+ "agent-relay-sdk": "0.2.18"
37
37
  },
38
38
  "scripts": {
39
39
  "prepack": "bun run build:dashboard:bundle >&2",
package/public/index.html CHANGED
@@ -128066,8 +128066,42 @@ function BusyIndicator({ blockedLabel, onInterrupt }) {
128066
128066
  })
128067
128067
  });
128068
128068
  }
128069
+ var HIDE_TOOLS_KEY = "agent-relay:chat-hide-tools";
128070
+ var hideToolsStore = (() => {
128071
+ const listeners = /* @__PURE__ */ new Set();
128072
+ let value = (() => {
128073
+ try {
128074
+ return window.localStorage.getItem(HIDE_TOOLS_KEY) === "on";
128075
+ } catch {
128076
+ return false;
128077
+ }
128078
+ })();
128079
+ const emit = () => listeners.forEach((l) => l());
128080
+ if (typeof window !== "undefined") window.addEventListener("storage", (e) => {
128081
+ if (e.key !== HIDE_TOOLS_KEY) return;
128082
+ value = e.newValue === "on";
128083
+ emit();
128084
+ });
128085
+ return {
128086
+ subscribe(cb) {
128087
+ listeners.add(cb);
128088
+ return () => listeners.delete(cb);
128089
+ },
128090
+ get: () => value,
128091
+ toggle() {
128092
+ value = !value;
128093
+ try {
128094
+ window.localStorage.setItem(HIDE_TOOLS_KEY, value ? "on" : "off");
128095
+ } catch {}
128096
+ emit();
128097
+ }
128098
+ };
128099
+ })();
128100
+ function useHideTools() {
128101
+ return [(0, import_react.useSyncExternalStore)(hideToolsStore.subscribe, hideToolsStore.get, () => false), hideToolsStore.toggle];
128102
+ }
128069
128103
  function ActivityTrace({ steps, showReasoning }) {
128070
- const [hideTools, setHideTools] = (0, import_react.useState)(false);
128104
+ const [hideTools, toggleHideTools] = useHideTools();
128071
128105
  const visible = showReasoning ? steps : steps.filter((s) => s.kind !== "reasoning");
128072
128106
  if (!visible.length) return null;
128073
128107
  const toolCount = visible.filter((s) => s.kind === "tool").length;
@@ -128102,7 +128136,7 @@ function ActivityTrace({ steps, showReasoning }) {
128102
128136
  })]
128103
128137
  }, step.id);
128104
128138
  }), toolCount > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("button", {
128105
- onClick: () => setHideTools((v) => !v),
128139
+ onClick: toggleHideTools,
128106
128140
  className: "flex items-center gap-1 text-[11px] text-muted-foreground/50 hover:text-muted-foreground transition-colors text-left",
128107
128141
  children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ChevronRight, { className: cn$2("w-3 h-3 shrink-0 transition-transform", !hideTools && "rotate-90") }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: hideTools ? `show ${toolCount} tool step${toolCount === 1 ? "" : "s"}` : "hide tool steps" })]
128108
128142
  })]
package/src/agent-ref.ts CHANGED
@@ -14,14 +14,14 @@
14
14
  import { STALE_TTL_MS } from "./config";
15
15
  import type { AgentCard } from "./types";
16
16
 
17
- export interface ResolveOptions {
17
+ interface ResolveOptions {
18
18
  /** Exclude this agent id from matches (e.g. the requester, when pairing). */
19
19
  excludeId?: string;
20
20
  /** Clock injection for tests. */
21
21
  now?: number;
22
22
  }
23
23
 
24
- export type ResolveResult =
24
+ type ResolveResult =
25
25
  | { status: "resolved"; agent: AgentCard }
26
26
  | { status: "ambiguous"; candidates: AgentCard[] }
27
27
  | { status: "not_found"; offlineMatches: AgentCard[] };
@@ -136,7 +136,7 @@ export interface DeliveryReceipt {
136
136
  reason?: string;
137
137
  }
138
138
 
139
- export type SendPlan =
139
+ type SendPlan =
140
140
  // resolved/fan-out/passthrough carry the (possibly rewritten) canonical `to`
141
141
  | { kind: "direct" | "fanout" | "passthrough"; to: string; receipt: DeliveryReceipt }
142
142
  | { kind: "not_found"; message: string }
package/src/cli.ts CHANGED
@@ -44,7 +44,7 @@ import {
44
44
  } from "./upgrade";
45
45
  import { formatMemoryBrokerSmokeResult, runMemoryBrokerSmoke } from "./memory-broker-smoke";
46
46
  import { MAX_BODY_BYTES, VERSION } from "./config";
47
- import { DEFAULT_CONTEXT_PROBE_STATE_DIR, runContextProbe } from "agent-relay-sdk/context-probe";
47
+ import { runContextProbe } from "agent-relay-sdk/context-probe";
48
48
  import { shellQuote } from "agent-relay-sdk/shell-utils";
49
49
  import { errMessage, RELAY_TOKEN_HEADER } from "agent-relay-sdk";
50
50
  import type { WorkspaceDepsRefreshResult } from "agent-relay-sdk";
@@ -686,7 +686,7 @@ async function handleContextProbeCommand(args: string[]): Promise<void> {
686
686
  let wrapCommand: string | undefined;
687
687
  let wrapRequested = false;
688
688
  let agentId: string | undefined;
689
- let stateDir = DEFAULT_CONTEXT_PROBE_STATE_DIR;
689
+ let stateDir: string | undefined;
690
690
  let standalone = false;
691
691
 
692
692
  for (let i = 0; i < inputArgs.length; i++) {
@@ -718,7 +718,7 @@ async function handleContextProbeCommand(args: string[]): Promise<void> {
718
718
  "context-probe",
719
719
  ...(wrapRequested ? ["--wrap", ...(wrapCommand ? [shellQuote(wrapCommand)] : [])] : ["--standalone"]),
720
720
  ...(agentId ? ["--agent-id", shellQuote(agentId)] : []),
721
- ...(stateDir !== DEFAULT_CONTEXT_PROBE_STATE_DIR ? ["--state-dir", shellQuote(stateDir)] : []),
721
+ ...(stateDir ? ["--state-dir", shellQuote(stateDir)] : []),
722
722
  ].join(" ");
723
723
  console.log(command);
724
724
  return;
package/src/contracts.ts CHANGED
@@ -21,7 +21,7 @@ export interface RuntimePackageMetadata {
21
21
  version: string;
22
22
  }
23
23
 
24
- export interface ContractRequirement {
24
+ interface ContractRequirement {
25
25
  min: number;
26
26
  maxExclusive: number;
27
27
  }
package/src/db.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Database } from "bun:sqlite";
2
2
  import { randomUUID } from "node:crypto";
3
- import { isRecord, stringValue } from "agent-relay-sdk";
3
+ import { isRecord, stringValue, isMechanicalMessageKind } from "agent-relay-sdk";
4
4
  import { ORCHESTRATOR_PROTOCOL_VERSION, VERSION } from "./config.ts";
5
5
  import { parseJson } from "./utils";
6
6
  import {
@@ -4331,7 +4331,7 @@ function messageRequiresReply(message: Message): boolean {
4331
4331
  // Server-owned notification flag (#283) wins over every kind/sender heuristic below: an
4332
4332
  // explicit replyExpected:false is a fire-and-forget message that must never become an obligation.
4333
4333
  if (message.replyExpected === false) return false;
4334
- if (message.kind === "system" || message.kind === "control" || message.kind === "session") return false;
4334
+ if (isMechanicalMessageKind(message.kind)) return false;
4335
4335
  if (message.from === "user") return true;
4336
4336
  if (message.kind === "task" || message.kind === "channel.event") return true;
4337
4337
  return Boolean(message.payload?.source);
@@ -4793,7 +4793,7 @@ export type AnalyticsPeriod = keyof typeof ANALYTICS_PERIODS;
4793
4793
  // Message → category, the ONE place this mapping lives (server-side SQL). Order is
4794
4794
  // significant: a claimable/system/pair/channel message is classified as such even
4795
4795
  // when it is also a reply; only an otherwise-plain reply counts as "Replies".
4796
- export const ANALYTICS_CATEGORIES = ["Messages", "Replies", "Work items", "System", "Pair", "Channel"] as const;
4796
+ const ANALYTICS_CATEGORIES = ["Messages", "Replies", "Work items", "System", "Pair", "Channel"] as const;
4797
4797
  export type AnalyticsCategory = (typeof ANALYTICS_CATEGORIES)[number];
4798
4798
  const ANALYTICS_CATEGORY_SQL = `
4799
4799
  CASE
@@ -5453,7 +5453,7 @@ export function setWorkspaceBranch(id: string, branch: string, baseSha?: string)
5453
5453
  // of these is a candidate steward; the repo is worth coordinating.
5454
5454
  const STEWARD_LIVE_STATUSES = "'active', 'ready', 'conflict', 'review_requested', 'merge_planned'";
5455
5455
 
5456
- export interface RepoStewardRecord {
5456
+ interface RepoStewardRecord {
5457
5457
  repoRoot: string;
5458
5458
  stewardAgentId?: string;
5459
5459
  lastStewardAgentId?: string;
@@ -5570,7 +5570,7 @@ function electWorkspaceStewardsForAgent(agentId: string): void {
5570
5570
 
5571
5571
  // --- Per-repo merge serialization lease (issue #157) -----------------------
5572
5572
 
5573
- export interface MergeLeaseRecord {
5573
+ interface MergeLeaseRecord {
5574
5574
  repoRoot: string;
5575
5575
  workspaceId: string;
5576
5576
  commandId?: string;
package/src/http-body.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** Concatenate body chunks into a single contiguous Uint8Array. */
2
- export function concatBytes(chunks: Uint8Array[]): Uint8Array {
2
+ function concatBytes(chunks: Uint8Array[]): Uint8Array {
3
3
  const total = chunks.reduce((sum, chunk) => sum + chunk.byteLength, 0);
4
4
  const output = new Uint8Array(total);
5
5
  let offset = 0;
@@ -46,7 +46,7 @@ function rowToObservation(row: ObservationRow): InsightObservation {
46
46
  };
47
47
  }
48
48
 
49
- export interface RecordObservationInput {
49
+ interface RecordObservationInput {
50
50
  sessionId: string;
51
51
  agentId?: string;
52
52
  project?: string;
@@ -99,7 +99,7 @@ export function getObservation(id: number): InsightObservation | null {
99
99
  return row ? rowToObservation(row) : null;
100
100
  }
101
101
 
102
- export interface ListObservationsQuery {
102
+ interface ListObservationsQuery {
103
103
  project?: string;
104
104
  signal?: string;
105
105
  sessionId?: string;
@@ -8,6 +8,7 @@ import {
8
8
  upsertManagedAgentState,
9
9
  } from "./config-store";
10
10
  import { emitRelayEvent } from "./events";
11
+ import { emitMessageDeliveryUpdated } from "./sse";
11
12
  import { emitCommandEvent } from "./command-events";
12
13
  import { buildManagedSpawnParams } from "./managed-policy";
13
14
  import { generateSpawnRequestId } from "./spawn-command";
@@ -95,6 +96,9 @@ export class LifecycleManager {
95
96
  subject: `policy:${meta.policyName}`,
96
97
  data: { policyName: meta.policyName, agentId, messageIds: available.map((message) => message.id), count: available.length },
97
98
  });
99
+ // queued → pending changed delivery_status; refresh the dashboard delivery
100
+ // badge now rather than letting it sit stale until the next poll (#265).
101
+ for (const message of available) emitMessageDeliveryUpdated(message);
98
102
  }
99
103
  }
100
104
 
@@ -20,7 +20,7 @@ export function effectiveManagedPolicyWorkspaceMode(policy: SpawnPolicy): Worksp
20
20
  return policy.binding?.type === "channel" ? "shared" : "inherit";
21
21
  }
22
22
 
23
- export interface ManagedSpawnContext {
23
+ interface ManagedSpawnContext {
24
24
  createdBy: string;
25
25
  requestedAt?: number;
26
26
  }
package/src/notify.ts CHANGED
@@ -2,7 +2,7 @@ import { sendMessage } from "./db";
2
2
  import { emitNewMessage } from "./sse";
3
3
  import type { Message, MessageKind } from "./types";
4
4
 
5
- export interface SystemNotifyOptions {
5
+ interface SystemNotifyOptions {
6
6
  subject?: string;
7
7
  body: string;
8
8
  payload?: Record<string, unknown>;
@@ -1,6 +1,6 @@
1
1
  import { listOrchestrators } from "./db";
2
2
 
3
- export interface ManagedAgentMatch {
3
+ interface ManagedAgentMatch {
4
4
  agentId?: string;
5
5
  sessionName?: string;
6
6
  tmuxSession?: string;
package/src/routes.ts CHANGED
@@ -163,7 +163,7 @@ import { planSend } from "./agent-ref";
163
163
  import { defaultProviderConfig, loadProviderConfig, providerConfigPublic, writeProviderConfig } from "../runner/src/config";
164
164
  import type { ProviderConfig } from "../runner/src/adapter";
165
165
  import { type ProviderEffort } from "agent-relay-sdk/provider-catalog";
166
- import { errMessage, isRecord, SPAWN_PROVIDERS, VALID_WORKSPACE_MODES, VALID_EFFORTS, APPROVAL_MODES, RELAY_TOKEN_HEADER } from "agent-relay-sdk";
166
+ import { errMessage, isRecord, SPAWN_PROVIDERS, VALID_WORKSPACE_MODES, VALID_EFFORTS, APPROVAL_MODES, RELAY_TOKEN_HEADER, isMechanicalMessageKind, isReservedAgentId } from "agent-relay-sdk";
167
167
  import { effectiveProviderCatalogList } from "./provider-catalog-store";
168
168
  import { buildManagedSpawnParams, effectiveManagedPolicyWorkspaceMode } from "./managed-policy";
169
169
  import { buildSpawnCommand, generateSpawnRequestId, resolveSpawnModelParams, type SpawnModelParams } from "./spawn-command";
@@ -1282,7 +1282,13 @@ const postAgent: Handler = async (req) => {
1282
1282
  const available = resolveQueuedPolicyMessages(policyName, agent.id);
1283
1283
  if (available.length) {
1284
1284
  emitMessageAvailable(policyName, agent.id, available);
1285
- for (const message of available) emitNewMessage(message);
1285
+ for (const message of available) {
1286
+ emitNewMessage(message);
1287
+ // queued → pending flips delivery_status; the dashboard dedups message.new
1288
+ // by id (the message already shows as "queued"), so without an explicit
1289
+ // delivery_updated the badge stays stale until the next poll (#265).
1290
+ emitMessageDeliveryUpdated(message);
1291
+ }
1286
1292
  }
1287
1293
  }
1288
1294
  }
@@ -5181,9 +5187,20 @@ const postMessage: Handler = async (req) => {
5181
5187
  }
5182
5188
  applyReplyRouting(input);
5183
5189
  if (!input.to) return error("to is required (or provide replyTo to auto-route)");
5190
+ // Mechanical lifecycle/observability posts (system/control/session) addressed to a
5191
+ // reserved sink ("user"/"system") are the relay's own lane, not agent-directed
5192
+ // messaging. A managed token's recipient constraints (targets/policies/agents) gate
5193
+ // which *agents* it may message — they must NOT gate a session-mirror capture to the
5194
+ // reserved sink, or constrained tokens (telegram policy, codex steward) 403 → the
5195
+ // runner's outbox retries 12× and poisons the record → the dashboard silently loses
5196
+ // the turn (#284, same outbox-poison failure mode as #184). The message:send scope
5197
+ // and any channel constraint still apply; we only drop the target/agentId predicate.
5198
+ const reservedSinkPost = isMechanicalMessageKind(input.kind) && isReservedAgentId(input.to);
5184
5199
  const denied = authorizeRoute(req, {
5185
5200
  scope: "message:send",
5186
- resource: { target: input.to, channel: input.channel, agentId: input.from },
5201
+ resource: reservedSinkPost
5202
+ ? { channel: input.channel }
5203
+ : { target: input.to, channel: input.channel, agentId: input.from },
5187
5204
  });
5188
5205
  if (denied) return denied;
5189
5206
  // Resolve the target through the shared planner — the SAME matcher the MCP send tool
@@ -5198,8 +5215,7 @@ const postMessage: Handler = async (req) => {
5198
5215
  // "session" = observed assistant turn (Phase 1 live-session lane). It is captured
5199
5216
  // from the provider transcript and stored for the dashboard chat; it must persist
5200
5217
  // regardless of target liveness and never be re-delivered into a session.
5201
- const bypassKinds = ["system", "control", "session"];
5202
- if (!bypassKinds.includes(input.kind ?? "")) {
5218
+ if (!isMechanicalMessageKind(input.kind)) {
5203
5219
  const plan = planSend(input.to, listAgents());
5204
5220
  if (plan.kind === "ambiguous") return error(plan.message, 409);
5205
5221
  if (plan.kind !== "not_found") input.to = plan.to;
@@ -5249,7 +5265,7 @@ const postMessage: Handler = async (req) => {
5249
5265
  };
5250
5266
 
5251
5267
  function automaticMemoryTarget(message: { to: string; resolvedToAgent?: string; kind: string }): string | null {
5252
- if (message.kind === "system" || message.kind === "control" || message.kind === "session") return null;
5268
+ if (isMechanicalMessageKind(message.kind)) return null;
5253
5269
  const target = message.resolvedToAgent ?? message.to;
5254
5270
  if (!isDirectTarget(target)) return null;
5255
5271
  const agent = getAgent(target);
@@ -118,7 +118,7 @@ export function issueRunnerRuntimeToken(input: {
118
118
  });
119
119
  }
120
120
 
121
- export function issueChildRunnerRuntimeToken(input: {
121
+ function issueChildRunnerRuntimeToken(input: {
122
122
  parentAgentId: string;
123
123
  orchestratorId: string;
124
124
  cwd: string;
@@ -239,7 +239,7 @@ export function runnerRuntimeTokenEnv(input: {
239
239
  };
240
240
  }
241
241
 
242
- export function childRunnerRuntimeTokenEnv(input: {
242
+ function childRunnerRuntimeTokenEnv(input: {
243
243
  parentAgentId: string;
244
244
  orchestratorId: string;
245
245
  cwd: string;
package/src/security.ts CHANGED
@@ -200,6 +200,9 @@ export function requiredScopeFor(method: string, pathname: string): string | nul
200
200
  if (pathname.startsWith("/api/maintenance")) return "system:admin";
201
201
  if (pathname.startsWith("/api/tasks")) return method === "GET" ? "task:read" : "task:write";
202
202
  if (pathname.startsWith("/api/pairs")) return method === "GET" ? "pairs:read" : "pairs:write";
203
+ // Insights config (the feature toggle) stays admin-only via the default; only the
204
+ // mechanical observation feed is writable by lower-privilege callers.
205
+ if (pathname === "/api/insights/observations") return method === "GET" ? "insights:read" : "insights:write";
203
206
  if (pathname.startsWith("/api/system/")) return "system:admin";
204
207
  return null;
205
208
  }
@@ -268,6 +271,11 @@ export function requiredComponentScopeFor(method: string, pathname: string): str
268
271
  if (pathname.startsWith("/api/agent-profiles")) return method === "GET" ? "agent:read" : "agent:write";
269
272
  if (pathname.startsWith("/api/tasks")) return method === "GET" ? "task:read" : "task:write";
270
273
  if (pathname.startsWith("/api/orchestrators")) return method === "GET" ? "agent:read" : "command:write";
274
+ // The Runner posts the #184 context-gathering signal here (source:"server") via its
275
+ // provider token. Without this case the path fell through to the system:admin default
276
+ // below and every observation was 403-dropped — the whole Insights feed stayed empty.
277
+ // Config (the feature toggle) intentionally keeps falling through to system:admin.
278
+ if (pathname === "/api/insights/observations") return method === "GET" ? "insights:read" : "insights:write";
271
279
  if (pathname.startsWith("/api/system/")) return "system:admin";
272
280
  return "system:admin";
273
281
  }
@@ -17,7 +17,7 @@ export function generateSpawnRequestId(): string {
17
17
  return `sp_${randomUUID()}`;
18
18
  }
19
19
 
20
- export interface ResolveSpawnModelParamsOptions {
20
+ interface ResolveSpawnModelParamsOptions {
21
21
  /**
22
22
  * What to do when provider-catalog resolution throws (e.g. unknown model, or
23
23
  * effort without a model):
@@ -69,7 +69,7 @@ export function resolveSpawnModelParams(
69
69
  * Every field that can appear in a spawn command-bus payload.
70
70
  * Optional fields are omitted from the result when undefined.
71
71
  */
72
- export interface BuildSpawnCommandOptions {
72
+ interface BuildSpawnCommandOptions {
73
73
  provider: SpawnProvider | string;
74
74
  cwd: string;
75
75
  /** Correlation id; omitted from the payload when absent (e.g. automation spawns use automationRunId instead). */
package/src/token-db.ts CHANGED
@@ -57,7 +57,7 @@ const BUILT_IN_PROFILES: Array<Omit<TokenProfile, "createdAt" | "updatedAt">> =
57
57
  name: "Provider Agent",
58
58
  description: "Coding-agent runtime access for messages, commands, tasks, and scoped memory reads.",
59
59
  role: "provider",
60
- scope: ["agent:read", "agent:write", "message:read", "message:send", "command:read", "command:write", "task:read", "task:write", "memory:read", "artifact:read", "artifact:write", "mcp:use"],
60
+ scope: ["agent:read", "agent:write", "message:read", "message:send", "command:read", "command:write", "task:read", "task:write", "memory:read", "artifact:read", "artifact:write", "mcp:use", "insights:write"],
61
61
  ttlSeconds: 24 * 60 * 60,
62
62
  builtIn: true,
63
63
  createdBy: "system",
@@ -67,7 +67,7 @@ const BUILT_IN_PROFILES: Array<Omit<TokenProfile, "createdAt" | "updatedAt">> =
67
67
  name: "Provider Child Agent",
68
68
  description: "Delegated child-agent runtime access, constrained to its parent and spawn request.",
69
69
  role: "provider",
70
- scope: ["agent:read", "agent:write", "message:read", "message:send", "command:read", "command:write", "task:read", "task:write", "memory:read", "artifact:read", "artifact:write", "mcp:use"],
70
+ scope: ["agent:read", "agent:write", "message:read", "message:send", "command:read", "command:write", "task:read", "task:write", "memory:read", "artifact:read", "artifact:write", "mcp:use", "insights:write"],
71
71
  constraints: { canDelegate: false },
72
72
  ttlSeconds: 2 * 60 * 60,
73
73
  builtIn: true,
@@ -78,7 +78,7 @@ const BUILT_IN_PROFILES: Array<Omit<TokenProfile, "createdAt" | "updatedAt">> =
78
78
  name: "Provider Interactive Agent",
79
79
  description: "User-launched provider runtime access constrained to its own agent and cwd for long interactive sessions.",
80
80
  role: "provider",
81
- scope: ["agent:read", "agent:write", "message:read", "message:send", "command:read", "command:write", "task:read", "task:write", "memory:read", "artifact:read", "artifact:write", "mcp:use"],
81
+ scope: ["agent:read", "agent:write", "message:read", "message:send", "command:read", "command:write", "task:read", "task:write", "memory:read", "artifact:read", "artifact:write", "mcp:use", "insights:write"],
82
82
  constraints: { terminalAttach: false, logsRead: false, canDelegate: false },
83
83
  ttlSeconds: 30 * 24 * 60 * 60,
84
84
  builtIn: true,
package/src/upgrade.ts CHANGED
@@ -249,7 +249,7 @@ export function createUpgradePlan(snapshot: UpgradeSnapshot, options: UpgradeOpt
249
249
  };
250
250
  }
251
251
 
252
- export type ExecuteUpgradeOptions = {
252
+ type ExecuteUpgradeOptions = {
253
253
  dryRun?: boolean;
254
254
  runner?: Runner;
255
255
  /** Re-register grace window for post-restart version checks (default 30s). */
@@ -41,7 +41,7 @@ export const WORKSPACE_ACTIONS = [
41
41
  ] as const;
42
42
  export type WorkspaceAction = (typeof WORKSPACE_ACTIONS)[number];
43
43
 
44
- export interface ApplyWorkspaceActionInput {
44
+ interface ApplyWorkspaceActionInput {
45
45
  action: WorkspaceAction;
46
46
  agentId?: string;
47
47
  detail?: string;
@@ -62,7 +62,7 @@ export interface ApplyWorkspaceActionInput {
62
62
  auditMetadata?: Record<string, unknown>;
63
63
  }
64
64
 
65
- export type WorkspaceActionResult =
65
+ type WorkspaceActionResult =
66
66
  | {
67
67
  ok: true;
68
68
  httpStatus: number;
@@ -320,10 +320,10 @@ export function buildWorkspaceDepsRefreshCommand(
320
320
  return { ok: true, command };
321
321
  }
322
322
 
323
- export const DEFAULT_WORKSPACE_WAIT_MS = 300_000;
324
- export const MAX_WORKSPACE_WAIT_MS = 600_000;
323
+ const DEFAULT_WORKSPACE_WAIT_MS = 300_000;
324
+ const MAX_WORKSPACE_WAIT_MS = 600_000;
325
325
 
326
- export interface WaitForWorkspaceResult {
326
+ interface WaitForWorkspaceResult {
327
327
  workspace: WorkspaceRecord | null;
328
328
  /** The status when the wait began. */
329
329
  fromStatus?: WorkspaceStatus;
@@ -4,9 +4,9 @@ import type { WorkspaceRecord } from "./types";
4
4
  // auto-merge (Layer 0) doesn't race it (#208 / steward report §1). The claim is a
5
5
  // TTL'd lease stored in row metadata, so a dead steward can't block the workspace
6
6
  // forever — it expires and auto-merge resumes. Renew by re-claiming.
7
- export const STEWARD_CLAIM_TTL_MS = Number(process.env.AGENT_RELAY_WORKSPACE_CLAIM_TTL_MS) || 15 * 60_000;
7
+ const STEWARD_CLAIM_TTL_MS = Number(process.env.AGENT_RELAY_WORKSPACE_CLAIM_TTL_MS) || 15 * 60_000;
8
8
 
9
- export interface WorkspaceClaim {
9
+ interface WorkspaceClaim {
10
10
  by?: string;
11
11
  purpose?: string;
12
12
  claimedAt?: number;
@@ -10,7 +10,7 @@ import {
10
10
  import type { Command, WorkspaceMergeStrategy, WorkspaceRecord } from "./types";
11
11
  import { isPathWithinBase } from "./utils";
12
12
 
13
- export interface RequestWorkspaceMergeOptions {
13
+ interface RequestWorkspaceMergeOptions {
14
14
  /** Who asked for the merge (lease holder + audit). e.g. an agent id, "dashboard", "auto-merge". */
15
15
  requestedBy: string;
16
16
  /** Merge strategy; "auto" lets the host pick pr-vs-rebase-ff. Defaults to "auto". */
@@ -26,7 +26,7 @@ export interface RequestWorkspaceMergeOptions {
26
26
  metadata?: Record<string, unknown>;
27
27
  }
28
28
 
29
- export type RequestWorkspaceMergeResult =
29
+ type RequestWorkspaceMergeResult =
30
30
  | { ok: true; command: Command; workspace: WorkspaceRecord }
31
31
  | { ok: false; status: number; error: string };
32
32
 
@@ -141,7 +141,7 @@ function knownRepoRoots(workspaces: WorkspaceRecord[]): string[] {
141
141
  return [...new Set(workspaces.map((ws) => ws.repoRoot).filter(Boolean))];
142
142
  }
143
143
 
144
- export interface CollectOrphansResult {
144
+ interface CollectOrphansResult {
145
145
  orphans: WorkspaceOrphan[];
146
146
  /** Live isolated rows whose worktree is missing on disk (DB→disk drift). */
147
147
  missingWorktrees: Array<{
@@ -71,7 +71,7 @@ export function worktreeReapable(state: WorktreeReapState | null | undefined): b
71
71
  // instead of the old behavior where it looked healthy for 90 minutes.
72
72
  export const LAND_PENDING_STALL_MS = 15 * 60 * 1000;
73
73
 
74
- export type WorkspacePhase =
74
+ type WorkspacePhase =
75
75
  | "working" // active — your turn: commit, then mark ready
76
76
  | "land-pending" // ready | review_requested — handed off; auto-merge will land it
77
77
  | "landing" // merge_planned — merge dispatched, in progress
@@ -79,7 +79,7 @@ export type WorkspacePhase =
79
79
  | "landed" // merged — on the base; a fresh rebased branch is coming
80
80
  | "closed"; // abandoned | cleanup_requested | cleaned — torn down
81
81
 
82
- export interface WorkspaceNextAction {
82
+ interface WorkspaceNextAction {
83
83
  /** MCP tool to call (when the agent is on the MCP surface). */
84
84
  tool?: string;
85
85
  /** Equivalent CLI invocation (when the agent is on the shell surface). */