agent-relay-server 0.30.0 → 0.31.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 +1 -1
- package/public/index.html +263 -73
- package/runner/src/config.ts +33 -0
- package/src/cli.ts +6 -2
- package/src/upgrade.ts +15 -4
- package/src/workspace-phase.ts +8 -4
package/package.json
CHANGED
package/public/index.html
CHANGED
|
@@ -12425,6 +12425,9 @@ var useRelayStore = create$1()(persist((set, get) => ({
|
|
|
12425
12425
|
spawnWorkspaceMode: "inherit",
|
|
12426
12426
|
spawnPrompt: "",
|
|
12427
12427
|
spawnSystemPromptAppend: "",
|
|
12428
|
+
spawnCount: 1,
|
|
12429
|
+
spawnCwdHistory: [],
|
|
12430
|
+
isSpawning: false,
|
|
12428
12431
|
spawnDirBrowser: {
|
|
12429
12432
|
open: false,
|
|
12430
12433
|
loading: false,
|
|
@@ -13925,6 +13928,7 @@ var useRelayStore = create$1()(persist((set, get) => ({
|
|
|
13925
13928
|
spawnWorkspaceMode: "inherit",
|
|
13926
13929
|
spawnPrompt: "",
|
|
13927
13930
|
spawnSystemPromptAppend: "",
|
|
13931
|
+
spawnCount: 1,
|
|
13928
13932
|
pendingForkImport: null,
|
|
13929
13933
|
orchestratorSpawnOpen: true
|
|
13930
13934
|
});
|
|
@@ -13974,6 +13978,7 @@ var useRelayStore = create$1()(persist((set, get) => ({
|
|
|
13974
13978
|
spawnWorkspaceMode: "inherit",
|
|
13975
13979
|
spawnPrompt: "Continue from this previous session.",
|
|
13976
13980
|
spawnSystemPromptAppend: systemPromptAppend,
|
|
13981
|
+
spawnCount: 1,
|
|
13977
13982
|
pendingForkImport: {
|
|
13978
13983
|
sourcePeerId: agent.id,
|
|
13979
13984
|
sourceAgentId: agent.id,
|
|
@@ -14005,12 +14010,35 @@ var useRelayStore = create$1()(persist((set, get) => ({
|
|
|
14005
14010
|
spawnWorkspaceMode: "inherit",
|
|
14006
14011
|
spawnPrompt: "",
|
|
14007
14012
|
spawnSystemPromptAppend: "",
|
|
14013
|
+
spawnCount: 1,
|
|
14008
14014
|
pendingForkImport: null,
|
|
14009
14015
|
orchestratorSpawnOpen: true
|
|
14010
14016
|
});
|
|
14011
14017
|
},
|
|
14018
|
+
selectSpawnHistory(orchId, cwd) {
|
|
14019
|
+
const s = get();
|
|
14020
|
+
const orch = s.orchestrators.find((o) => o.id === orchId && o.status === "online");
|
|
14021
|
+
if (!orch) {
|
|
14022
|
+
get().showError("Unavailable", "That orchestrator is no longer online.");
|
|
14023
|
+
return;
|
|
14024
|
+
}
|
|
14025
|
+
if (orchId === s.spawnOrchId) {
|
|
14026
|
+
set({ spawnCwd: cwd });
|
|
14027
|
+
return;
|
|
14028
|
+
}
|
|
14029
|
+
const provider = s.spawnProvider && orch.providers.includes(s.spawnProvider) ? s.spawnProvider : firstAvailableProvider(orch);
|
|
14030
|
+
const model = s.spawnProvider === provider && s.spawnModel ? s.spawnModel : defaultModelFor(provider);
|
|
14031
|
+
set({
|
|
14032
|
+
spawnOrchId: orchId,
|
|
14033
|
+
spawnProvider: provider,
|
|
14034
|
+
spawnModel: model,
|
|
14035
|
+
spawnEffort: s.spawnProvider === provider && s.spawnModel === model ? s.spawnEffort : "",
|
|
14036
|
+
spawnCwd: cwd
|
|
14037
|
+
});
|
|
14038
|
+
},
|
|
14012
14039
|
async submitOrchestratorSpawn() {
|
|
14013
14040
|
const s = get();
|
|
14041
|
+
if (s.isSpawning) return;
|
|
14014
14042
|
if (!s.spawnOrchId) {
|
|
14015
14043
|
get().showError("Validation", "Select an orchestrator.");
|
|
14016
14044
|
return;
|
|
@@ -14029,34 +14057,42 @@ var useRelayStore = create$1()(persist((set, get) => ({
|
|
|
14029
14057
|
get().showError("Validation", `Working directory must be under ${orch.baseDir}.`);
|
|
14030
14058
|
return;
|
|
14031
14059
|
}
|
|
14060
|
+
const pendingForkImport = s.pendingForkImport;
|
|
14061
|
+
const count = pendingForkImport ? 1 : Math.max(1, Math.min(10, Math.round(s.spawnCount) || 1));
|
|
14062
|
+
set({ isSpawning: true });
|
|
14032
14063
|
try {
|
|
14033
|
-
|
|
14034
|
-
|
|
14035
|
-
|
|
14036
|
-
|
|
14037
|
-
|
|
14038
|
-
|
|
14039
|
-
|
|
14040
|
-
|
|
14041
|
-
|
|
14042
|
-
|
|
14043
|
-
|
|
14044
|
-
|
|
14045
|
-
|
|
14046
|
-
|
|
14047
|
-
|
|
14048
|
-
|
|
14049
|
-
|
|
14050
|
-
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
14054
|
-
|
|
14055
|
-
|
|
14056
|
-
}
|
|
14064
|
+
for (let i = 0; i < count; i++) {
|
|
14065
|
+
const payload = {
|
|
14066
|
+
provider: s.spawnProvider,
|
|
14067
|
+
approvalMode: s.spawnApproval,
|
|
14068
|
+
workspaceMode: s.spawnWorkspaceMode || "inherit"
|
|
14069
|
+
};
|
|
14070
|
+
if (s.spawnModel) payload.model = s.spawnModel;
|
|
14071
|
+
if (s.spawnEffort) payload.effort = s.spawnEffort;
|
|
14072
|
+
if (s.spawnProfile) payload.profile = s.spawnProfile;
|
|
14073
|
+
payload.cwd = cwd;
|
|
14074
|
+
if (s.spawnLabel) payload.label = count > 1 ? `${s.spawnLabel} ${i + 1}` : s.spawnLabel;
|
|
14075
|
+
if (s.spawnPrompt) payload.prompt = s.spawnPrompt;
|
|
14076
|
+
if (s.spawnSystemPromptAppend) payload.systemPromptAppend = s.spawnSystemPromptAppend;
|
|
14077
|
+
const targetSpawnRequestId = (await api("POST", "/orchestrators/" + encodeURIComponent(s.spawnOrchId) + "/spawn", payload)).command?.params?.spawnRequestId;
|
|
14078
|
+
if (pendingForkImport && targetSpawnRequestId && pendingForkImport.messageIds.length > 0) await api("POST", "/chat/history-imports", {
|
|
14079
|
+
targetSpawnRequestId,
|
|
14080
|
+
sourcePeerId: pendingForkImport.sourcePeerId,
|
|
14081
|
+
sourceAgentId: pendingForkImport.sourceAgentId,
|
|
14082
|
+
sourceThreadId: pendingForkImport.sourceThreadId,
|
|
14083
|
+
sourceAgentLabel: pendingForkImport.sourceAgentLabel,
|
|
14084
|
+
importedBy: INBOX_OPERATOR_ID,
|
|
14085
|
+
messageIds: pendingForkImport.messageIds
|
|
14086
|
+
});
|
|
14087
|
+
}
|
|
14057
14088
|
set({
|
|
14058
14089
|
orchestratorSpawnOpen: false,
|
|
14059
|
-
pendingForkImport: null
|
|
14090
|
+
pendingForkImport: null,
|
|
14091
|
+
isSpawning: false,
|
|
14092
|
+
spawnCwdHistory: [{
|
|
14093
|
+
orchId: s.spawnOrchId,
|
|
14094
|
+
cwd
|
|
14095
|
+
}, ...s.spawnCwdHistory.filter((h) => !(h.orchId === s.spawnOrchId && h.cwd === cwd))].slice(0, 5)
|
|
14060
14096
|
});
|
|
14061
14097
|
await Promise.all([
|
|
14062
14098
|
get().fetchAgents(),
|
|
@@ -14065,6 +14101,7 @@ var useRelayStore = create$1()(persist((set, get) => ({
|
|
|
14065
14101
|
get().fetchChatHistoryImports()
|
|
14066
14102
|
]);
|
|
14067
14103
|
} catch (e) {
|
|
14104
|
+
set({ isSpawning: false });
|
|
14068
14105
|
get().showError("Spawn Failed", e.message);
|
|
14069
14106
|
}
|
|
14070
14107
|
},
|
|
@@ -14710,7 +14747,8 @@ var useRelayStore = create$1()(persist((set, get) => ({
|
|
|
14710
14747
|
spawnProfile: state.spawnProfile,
|
|
14711
14748
|
spawnCwd: state.spawnCwd,
|
|
14712
14749
|
spawnApproval: state.spawnApproval,
|
|
14713
|
-
spawnWorkspaceMode: state.spawnWorkspaceMode
|
|
14750
|
+
spawnWorkspaceMode: state.spawnWorkspaceMode,
|
|
14751
|
+
spawnCwdHistory: state.spawnCwdHistory
|
|
14714
14752
|
})
|
|
14715
14753
|
}));
|
|
14716
14754
|
var _voiceActiveChat = null;
|
|
@@ -77210,7 +77248,7 @@ function SidebarContent() {
|
|
|
77210
77248
|
}
|
|
77211
77249
|
function Sidebar() {
|
|
77212
77250
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", {
|
|
77213
|
-
className: "hidden xl:flex w-[250px] min-w-[250px] h-
|
|
77251
|
+
className: "hidden xl:flex w-[250px] min-w-[250px] h-[calc(100dvh-var(--sat))] sticky top-[var(--sat)] flex-col border-r border-border bg-card",
|
|
77214
77252
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
77215
77253
|
className: "px-5 py-4 font-bold text-base border-b border-border flex items-center gap-2",
|
|
77216
77254
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Radio, { className: "w-4 h-4 text-primary" }), "Agent Relay"]
|
|
@@ -77232,7 +77270,7 @@ function MobileDrawer() {
|
|
|
77232
77270
|
className: cn$2("xl:hidden fixed inset-0 z-40 bg-black/50 transition-opacity duration-200", open ? "opacity-100" : "opacity-0 pointer-events-none"),
|
|
77233
77271
|
onClick: () => set({ mobileMenuOpen: false })
|
|
77234
77272
|
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("aside", {
|
|
77235
|
-
className: cn$2("xl:hidden fixed inset-y-0 left-0 z-50 w-[280px] flex flex-col bg-card border-r border-border transition-transform duration-200 ease-out", open ? "translate-x-0" : "-translate-x-full"),
|
|
77273
|
+
className: cn$2("xl:hidden fixed inset-y-0 left-0 z-50 w-[280px] flex flex-col pt-[var(--sat)] bg-card border-r border-border transition-transform duration-200 ease-out", open ? "translate-x-0" : "-translate-x-full"),
|
|
77236
77274
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
77237
77275
|
className: "px-5 py-4 font-bold text-base border-b border-border flex items-center gap-2",
|
|
77238
77276
|
children: [
|
|
@@ -77735,6 +77773,60 @@ function useAgentTerminal(agentId) {
|
|
|
77735
77773
|
};
|
|
77736
77774
|
}
|
|
77737
77775
|
//#endregion
|
|
77776
|
+
//#region src/hooks/use-keyboard-viewport.ts
|
|
77777
|
+
var KEYBOARD_MIN_DELTA = 100;
|
|
77778
|
+
function isEditableTarget(el) {
|
|
77779
|
+
if (!el) return false;
|
|
77780
|
+
const tag = el.tagName;
|
|
77781
|
+
return tag === "INPUT" || tag === "TEXTAREA" || el.isContentEditable === true;
|
|
77782
|
+
}
|
|
77783
|
+
/**
|
|
77784
|
+
* Returns a pixel height to apply while the on-screen keyboard is open, or null
|
|
77785
|
+
* when it's closed (so the element falls back to its CSS height, e.g. 100dvh).
|
|
77786
|
+
*
|
|
77787
|
+
* iPad Safari ignores `interactive-widget=resizes-content`, so the keyboard
|
|
77788
|
+
* shrinks only the *visual* viewport, never the layout viewport — `dvh` units
|
|
77789
|
+
* don't react to it. Pinning `visualViewport.height` is what actually makes a
|
|
77790
|
+
* fullscreen container shrink for the keyboard there.
|
|
77791
|
+
*
|
|
77792
|
+
* The trap (#270 follow-up): if you pin `vv.height` unconditionally and never
|
|
77793
|
+
* reset it, the container stays shrunk after the keyboard closes, and iOS's
|
|
77794
|
+
* documented post-dismiss bug (a late resize event reporting wrong/offset
|
|
77795
|
+
* dimensions) gets applied blindly — leaving the layout too narrow / dialogs
|
|
77796
|
+
* too tall until an orientation toggle resets the OS viewport.
|
|
77797
|
+
*
|
|
77798
|
+
* So we pin a height ONLY while the keyboard is genuinely open (an editable
|
|
77799
|
+
* element is focused AND the visual viewport is meaningfully shorter than the
|
|
77800
|
+
* layout viewport) and return null otherwise. Returning null hands sizing back
|
|
77801
|
+
* to CSS, which recovers automatically — the same reset the orientation toggle
|
|
77802
|
+
* triggers, minus the manual flip. This also no-ops for iPad hardware keyboards
|
|
77803
|
+
* (no visual-viewport shrink → no spurious pin).
|
|
77804
|
+
*
|
|
77805
|
+
* @param active when false the hook stays inert and returns null (e.g. a closed dialog)
|
|
77806
|
+
*/
|
|
77807
|
+
function useKeyboardViewportHeight(active = true) {
|
|
77808
|
+
const [height, setHeight] = (0, import_react.useState)(null);
|
|
77809
|
+
(0, import_react.useEffect)(() => {
|
|
77810
|
+
if (!active) {
|
|
77811
|
+
setHeight(null);
|
|
77812
|
+
return;
|
|
77813
|
+
}
|
|
77814
|
+
const vv = window.visualViewport;
|
|
77815
|
+
if (!vv) return;
|
|
77816
|
+
function onResize() {
|
|
77817
|
+
setHeight(isEditableTarget(document.activeElement) && window.innerHeight - vv.height > KEYBOARD_MIN_DELTA ? vv.height : null);
|
|
77818
|
+
}
|
|
77819
|
+
onResize();
|
|
77820
|
+
vv.addEventListener("resize", onResize);
|
|
77821
|
+
window.addEventListener("focusout", onResize);
|
|
77822
|
+
return () => {
|
|
77823
|
+
vv.removeEventListener("resize", onResize);
|
|
77824
|
+
window.removeEventListener("focusout", onResize);
|
|
77825
|
+
};
|
|
77826
|
+
}, [active]);
|
|
77827
|
+
return height;
|
|
77828
|
+
}
|
|
77829
|
+
//#endregion
|
|
77738
77830
|
//#region node_modules/comma-separated-tokens/index.js
|
|
77739
77831
|
/**
|
|
77740
77832
|
* Serialize an array of strings or numbers to comma-separated tokens.
|
|
@@ -126441,11 +126533,13 @@ function TerminalViewer({ orchestratorId, session, interactive: initialInteracti
|
|
|
126441
126533
|
});
|
|
126442
126534
|
}
|
|
126443
126535
|
function TerminalDialog({ open, onOpenChange, orchestratorId, session, interactive }) {
|
|
126536
|
+
const vpHeight = useKeyboardViewportHeight(open);
|
|
126444
126537
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog, {
|
|
126445
126538
|
open,
|
|
126446
126539
|
onOpenChange,
|
|
126447
126540
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogContent, {
|
|
126448
|
-
className: "inset-0 translate-x-0 translate-y-0 w-auto
|
|
126541
|
+
className: "inset-x-0 top-0 translate-x-0 translate-y-0 w-auto max-w-none rounded-none p-0 pt-[var(--sat)] bg-background border-border gap-0 overflow-hidden",
|
|
126542
|
+
style: { height: vpHeight ? `${vpHeight}px` : "100dvh" },
|
|
126449
126543
|
showCloseButton: false,
|
|
126450
126544
|
blurOverlay: false,
|
|
126451
126545
|
onEscapeKeyDown: (e) => e.preventDefault(),
|
|
@@ -128894,7 +128988,7 @@ var MessageBubble = (0, import_react.memo)(function MessageBubble({ msg, peer, o
|
|
|
128894
128988
|
type: "button",
|
|
128895
128989
|
title: isTtsPlaying ? "Stop playback" : "Play aloud",
|
|
128896
128990
|
onClick: togglePlay,
|
|
128897
|
-
className: cn$2("absolute -top-2 -right-2 z-20 inline-flex h-6 w-6 items-center justify-center rounded-full border bg-popover shadow-sm transition", isTtsPlaying ? "border-primary/50 text-primary opacity-100" : "border-border text-muted-foreground opacity-70 hover:bg-muted hover:text-foreground hover:opacity-100 md:opacity-0 md:group-hover/msg:opacity-100"),
|
|
128991
|
+
className: cn$2("absolute -top-2 -right-2 z-20 inline-flex h-6 w-6 items-center justify-center rounded-full border bg-popover shadow-sm transition", isTtsPlaying ? "border-primary/50 text-primary opacity-100" : "border-border text-muted-foreground opacity-70 hover:bg-muted hover:text-foreground hover:opacity-100 [@media(hover:hover)]:md:opacity-0 [@media(hover:hover)]:md:group-hover/msg:opacity-100"),
|
|
128898
128992
|
children: isTtsPlaying ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CircleStop, { className: "h-3.5 w-3.5 animate-pulse" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Volume2, { className: "h-3.5 w-3.5" })
|
|
128899
128993
|
}),
|
|
128900
128994
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
@@ -129692,7 +129786,7 @@ function ChatPanel({ threads, onBack, showBackButton }) {
|
|
|
129692
129786
|
onClick: interruptAgent,
|
|
129693
129787
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CircleStop, { className: "w-3.5 h-3.5" })
|
|
129694
129788
|
}),
|
|
129695
|
-
agent.branchState === "changes" && agent.branchWorkspaceId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
129789
|
+
(agent.branchState === "changes" || agent.branchState === "idle") && agent.branchWorkspaceId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
129696
129790
|
variant: "ghost",
|
|
129697
129791
|
size: "icon-sm",
|
|
129698
129792
|
className: "hidden @4xl/chat:inline-flex text-amber-400 hover:text-amber-300",
|
|
@@ -129787,7 +129881,7 @@ function ChatPanel({ threads, onBack, showBackButton }) {
|
|
|
129787
129881
|
onClick: interruptAgent,
|
|
129788
129882
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CircleStop, { className: "w-3.5 h-3.5" }), "Interrupt"]
|
|
129789
129883
|
}),
|
|
129790
|
-
agent.branchState === "changes" && agent.branchWorkspaceId && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenuItem, {
|
|
129884
|
+
(agent.branchState === "changes" || agent.branchState === "idle") && agent.branchWorkspaceId && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DropdownMenuItem, {
|
|
129791
129885
|
onClick: () => openConfirm("Mark Workspace Ready", `Mark ${displayName(agent)}'s branch ready to land? The relay auto-merge will rebase and land it.`, () => workspaceAction(agent.branchWorkspaceId, "ready")),
|
|
129792
129886
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Flag, { className: "w-3.5 h-3.5" }), "Mark Ready"]
|
|
129793
129887
|
}),
|
|
@@ -130113,7 +130207,7 @@ function ChatView() {
|
|
|
130113
130207
|
}
|
|
130114
130208
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
130115
130209
|
className: "flex -m-4 md:-m-6",
|
|
130116
|
-
style: { height: "calc(100dvh - var(--header-h))" },
|
|
130210
|
+
style: { height: "calc(100dvh - var(--header-h) - var(--sat))" },
|
|
130117
130211
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
130118
130212
|
className: "hidden md:flex w-full",
|
|
130119
130213
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
@@ -130137,19 +130231,9 @@ function ChatView() {
|
|
|
130137
130231
|
});
|
|
130138
130232
|
}
|
|
130139
130233
|
function MobileChatContainer({ children }) {
|
|
130140
|
-
const
|
|
130141
|
-
(0, import_react.useEffect)(() => {
|
|
130142
|
-
const vv = window.visualViewport;
|
|
130143
|
-
if (!vv) return;
|
|
130144
|
-
function onResize() {
|
|
130145
|
-
setHeight(vv.height);
|
|
130146
|
-
}
|
|
130147
|
-
onResize();
|
|
130148
|
-
vv.addEventListener("resize", onResize);
|
|
130149
|
-
return () => vv.removeEventListener("resize", onResize);
|
|
130150
|
-
}, []);
|
|
130234
|
+
const height = useKeyboardViewportHeight();
|
|
130151
130235
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
130152
|
-
className: "fixed inset-x-0 top-0 z-40 flex flex-col bg-background",
|
|
130236
|
+
className: "fixed inset-x-0 top-0 z-40 flex flex-col bg-background pt-[var(--sat)]",
|
|
130153
130237
|
style: { height: height ? `${height}px` : "100dvh" },
|
|
130154
130238
|
children
|
|
130155
130239
|
});
|
|
@@ -130313,7 +130397,7 @@ function AgentCard({ agent }) {
|
|
|
130313
130397
|
onClick: () => void handleOpenTerminal(),
|
|
130314
130398
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Terminal, { className: "w-3 h-3" })
|
|
130315
130399
|
}),
|
|
130316
|
-
agent.branchState === "changes" && agent.branchWorkspaceId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
130400
|
+
(agent.branchState === "changes" || agent.branchState === "idle") && agent.branchWorkspaceId && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
130317
130401
|
size: "icon",
|
|
130318
130402
|
variant: "ghost",
|
|
130319
130403
|
className: "h-7 w-7 text-amber-400 hover:text-amber-300",
|
|
@@ -155596,7 +155680,7 @@ function InsightsView() {
|
|
|
155596
155680
|
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
155597
155681
|
className: "overflow-x-auto",
|
|
155598
155682
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", {
|
|
155599
|
-
className: "w-full text-left text-xs",
|
|
155683
|
+
className: "w-full min-w-[680px] text-left text-xs",
|
|
155600
155684
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", {
|
|
155601
155685
|
className: "text-muted-foreground",
|
|
155602
155686
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", {
|
|
@@ -159356,15 +159440,23 @@ function OrchestratorSpawnModal() {
|
|
|
159356
159440
|
const workspaceMode = useRelayStore((s) => s.spawnWorkspaceMode);
|
|
159357
159441
|
const prompt = useRelayStore((s) => s.spawnPrompt);
|
|
159358
159442
|
const systemPromptAppend = useRelayStore((s) => s.spawnSystemPromptAppend);
|
|
159443
|
+
const count = useRelayStore((s) => s.spawnCount);
|
|
159444
|
+
const cwdHistory = useRelayStore((s) => s.spawnCwdHistory);
|
|
159445
|
+
const isSpawning = useRelayStore((s) => s.isSpawning);
|
|
159446
|
+
const isFork = useRelayStore((s) => Boolean(s.pendingForkImport));
|
|
159359
159447
|
const set = useRelayStore((s) => s.set);
|
|
159360
159448
|
const submitOrchestratorSpawn = useRelayStore((s) => s.submitOrchestratorSpawn);
|
|
159449
|
+
const selectSpawnHistory = useRelayStore((s) => s.selectSpawnHistory);
|
|
159361
159450
|
const agentProfiles = useRelayStore((s) => s.agentProfiles);
|
|
159362
159451
|
const orch = orchestrators.find((o) => o.id === orchId);
|
|
159363
159452
|
const providers = orch?.providers || [];
|
|
159364
159453
|
const models = PROVIDER_CATALOG[provider]?.models || [];
|
|
159365
159454
|
const selectedModel = models.find((m) => m.alias === model);
|
|
159366
159455
|
const efforts = selectedModel?.efforts || [];
|
|
159367
|
-
const canSpawn = Boolean(orch && providers.length > 0 && cwd.trim());
|
|
159456
|
+
const canSpawn = Boolean(orch && providers.length > 0 && cwd.trim()) && !isSpawning;
|
|
159457
|
+
const onlineOrchs = orchestrators.filter((o) => o.status === "online");
|
|
159458
|
+
const multiOrch = onlineOrchs.length > 1;
|
|
159459
|
+
const recentDirs = cwdHistory.filter((h) => onlineOrchs.some((o) => o.id === h.orchId)).slice(0, 5);
|
|
159368
159460
|
function selectOrchestrator(nextId) {
|
|
159369
159461
|
const nextOrch = orchestrators.find((o) => o.id === nextId);
|
|
159370
159462
|
const nextProvider = nextOrch?.providers[0] || "";
|
|
@@ -159394,6 +159486,7 @@ function OrchestratorSpawnModal() {
|
|
|
159394
159486
|
onOpenChange: (o) => !o && set({ orchestratorSpawnOpen: false }),
|
|
159395
159487
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogContent, {
|
|
159396
159488
|
className: "max-w-lg max-h-[90dvh] flex flex-col",
|
|
159489
|
+
onOpenAutoFocus: (e) => e.preventDefault(),
|
|
159397
159490
|
children: [
|
|
159398
159491
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle, { children: "Spawn Agent" }) }),
|
|
159399
159492
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
@@ -159502,15 +159595,39 @@ function OrchestratorSpawnModal() {
|
|
|
159502
159595
|
})
|
|
159503
159596
|
]
|
|
159504
159597
|
})] }),
|
|
159505
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
159506
|
-
|
|
159507
|
-
|
|
159508
|
-
|
|
159509
|
-
|
|
159510
|
-
|
|
159511
|
-
|
|
159598
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
159599
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { children: "Working Directory" }),
|
|
159600
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
159601
|
+
className: "mt-1",
|
|
159602
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DirectoryPicker, {
|
|
159603
|
+
value: cwd,
|
|
159604
|
+
onChange: (path) => set({ spawnCwd: path }),
|
|
159605
|
+
orchestratorId: orchId,
|
|
159606
|
+
placeholder: "Select a project directory"
|
|
159607
|
+
})
|
|
159608
|
+
}),
|
|
159609
|
+
recentDirs.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
159610
|
+
className: "mt-2",
|
|
159611
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
159612
|
+
className: "text-xs text-muted-foreground mb-1",
|
|
159613
|
+
children: "Recent"
|
|
159614
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
159615
|
+
className: "flex gap-1.5 overflow-x-auto pb-1 sm:flex-wrap sm:overflow-x-visible sm:pb-0",
|
|
159616
|
+
children: recentDirs.map((h) => {
|
|
159617
|
+
const base = h.cwd.replace(/\/+$/, "").split("/").pop() || h.cwd;
|
|
159618
|
+
const host = onlineOrchs.find((o) => o.id === h.orchId)?.hostname;
|
|
159619
|
+
const active = h.orchId === orchId && h.cwd === cwd;
|
|
159620
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", {
|
|
159621
|
+
type: "button",
|
|
159622
|
+
title: multiOrch && host ? `${host}: ${h.cwd}` : h.cwd,
|
|
159623
|
+
onClick: () => selectSpawnHistory(h.orchId, h.cwd),
|
|
159624
|
+
className: `shrink-0 rounded-full border px-2.5 py-1 text-xs font-mono transition-colors ${active ? "border-primary bg-primary/10 text-foreground" : "border-input bg-background text-muted-foreground hover:bg-accent hover:text-foreground"}`,
|
|
159625
|
+
children: multiOrch && host ? `${host}: ${base}` : base
|
|
159626
|
+
}, `${h.orchId}:${h.cwd}`);
|
|
159627
|
+
})
|
|
159628
|
+
})]
|
|
159512
159629
|
})
|
|
159513
|
-
|
|
159630
|
+
] }),
|
|
159514
159631
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { children: "Label" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Input, {
|
|
159515
159632
|
value: label,
|
|
159516
159633
|
onChange: (e) => set({ spawnLabel: e.target.value }),
|
|
@@ -159541,17 +159658,47 @@ function OrchestratorSpawnModal() {
|
|
|
159541
159658
|
className: "text-xs text-muted-foreground mt-1",
|
|
159542
159659
|
children: "Use for fork history or launch context. It informs the session but does not start work by itself."
|
|
159543
159660
|
})
|
|
159661
|
+
] }),
|
|
159662
|
+
!isFork && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
159663
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
159664
|
+
className: "flex items-center justify-between",
|
|
159665
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Label, { children: "Number of Agents" }), /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", {
|
|
159666
|
+
className: "text-sm font-mono tabular-nums text-muted-foreground",
|
|
159667
|
+
children: count
|
|
159668
|
+
})]
|
|
159669
|
+
}),
|
|
159670
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", {
|
|
159671
|
+
type: "range",
|
|
159672
|
+
min: 1,
|
|
159673
|
+
max: 10,
|
|
159674
|
+
step: 1,
|
|
159675
|
+
value: count,
|
|
159676
|
+
onChange: (e) => set({ spawnCount: Number(e.target.value) }),
|
|
159677
|
+
className: "w-full mt-2 accent-primary",
|
|
159678
|
+
"aria-label": "Number of agents to spawn"
|
|
159679
|
+
}),
|
|
159680
|
+
count > 1 && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
|
|
159681
|
+
className: "text-xs text-muted-foreground mt-1",
|
|
159682
|
+
children: [
|
|
159683
|
+
"Spawns ",
|
|
159684
|
+
count,
|
|
159685
|
+
" identical agents",
|
|
159686
|
+
label.trim() ? `, labelled "${label.trim()} 1"…"${label.trim()} ${count}"` : "",
|
|
159687
|
+
"."
|
|
159688
|
+
]
|
|
159689
|
+
})
|
|
159544
159690
|
] })
|
|
159545
159691
|
]
|
|
159546
159692
|
}),
|
|
159547
159693
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
159548
159694
|
variant: "outline",
|
|
159549
159695
|
onClick: () => set({ orchestratorSpawnOpen: false }),
|
|
159696
|
+
disabled: isSpawning,
|
|
159550
159697
|
children: "Cancel"
|
|
159551
159698
|
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
159552
159699
|
onClick: submitOrchestratorSpawn,
|
|
159553
159700
|
disabled: !canSpawn,
|
|
159554
|
-
children: providers.length ? "Spawn" : "No providers"
|
|
159701
|
+
children: isSpawning ? "Spawning…" : providers.length ? count > 1 ? `Spawn ${count}` : "Spawn" : "No providers"
|
|
159555
159702
|
})] })
|
|
159556
159703
|
]
|
|
159557
159704
|
})
|
|
@@ -160677,12 +160824,12 @@ function App() {
|
|
|
160677
160824
|
}, [commandPaletteOpen]);
|
|
160678
160825
|
const ViewComponent = views[view] || OverviewView;
|
|
160679
160826
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBoundary, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TooltipProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
160680
|
-
className: "flex min-h-
|
|
160827
|
+
className: "flex min-h-[calc(100dvh-var(--sat))] mt-[var(--sat)]",
|
|
160681
160828
|
children: [
|
|
160682
160829
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sidebar, {}),
|
|
160683
160830
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MobileDrawer, {}),
|
|
160684
160831
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", {
|
|
160685
|
-
className: "flex-1 h-
|
|
160832
|
+
className: "flex-1 min-w-0 h-[calc(100dvh-var(--sat))] overflow-y-auto",
|
|
160686
160833
|
children: [
|
|
160687
160834
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MobileNav, {}),
|
|
160688
160835
|
!authNeeded && connectionError && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
@@ -161305,6 +161452,10 @@ if ("serviceWorker" in navigator) {
|
|
|
161305
161452
|
top: calc(var(--header-h, 3rem) + .5rem);
|
|
161306
161453
|
}
|
|
161307
161454
|
|
|
161455
|
+
.top-\[var\(--sat\)\] {
|
|
161456
|
+
top: var(--sat);
|
|
161457
|
+
}
|
|
161458
|
+
|
|
161308
161459
|
.-right-0\.5 {
|
|
161309
161460
|
right: calc(var(--spacing) * -.5);
|
|
161310
161461
|
}
|
|
@@ -161507,6 +161658,10 @@ if ("serviceWorker" in navigator) {
|
|
|
161507
161658
|
margin-top: calc(var(--spacing) * 4);
|
|
161508
161659
|
}
|
|
161509
161660
|
|
|
161661
|
+
.mt-\[var\(--sat\)\] {
|
|
161662
|
+
margin-top: var(--sat);
|
|
161663
|
+
}
|
|
161664
|
+
|
|
161510
161665
|
.mt-auto {
|
|
161511
161666
|
margin-top: auto;
|
|
161512
161667
|
}
|
|
@@ -161761,16 +161916,12 @@ if ("serviceWorker" in navigator) {
|
|
|
161761
161916
|
height: calc(100dvh - 24rem);
|
|
161762
161917
|
}
|
|
161763
161918
|
|
|
161764
|
-
.h-\[var\(--
|
|
161765
|
-
height: var(--
|
|
161919
|
+
.h-\[calc\(100dvh-var\(--sat\)\)\] {
|
|
161920
|
+
height: calc(100dvh - var(--sat));
|
|
161766
161921
|
}
|
|
161767
161922
|
|
|
161768
|
-
.h-
|
|
161769
|
-
height:
|
|
161770
|
-
}
|
|
161771
|
-
|
|
161772
|
-
.h-dvh {
|
|
161773
|
-
height: 100dvh;
|
|
161923
|
+
.h-\[var\(--header-h\)\] {
|
|
161924
|
+
height: var(--header-h);
|
|
161774
161925
|
}
|
|
161775
161926
|
|
|
161776
161927
|
.h-fit {
|
|
@@ -161893,8 +162044,8 @@ if ("serviceWorker" in navigator) {
|
|
|
161893
162044
|
min-height: 360px;
|
|
161894
162045
|
}
|
|
161895
162046
|
|
|
161896
|
-
.min-h-
|
|
161897
|
-
min-height: 100dvh;
|
|
162047
|
+
.min-h-\[calc\(100dvh-var\(--sat\)\)\] {
|
|
162048
|
+
min-height: calc(100dvh - var(--sat));
|
|
161898
162049
|
}
|
|
161899
162050
|
|
|
161900
162051
|
.w-\(--radix-dropdown-menu-trigger-width\) {
|
|
@@ -162133,6 +162284,10 @@ if ("serviceWorker" in navigator) {
|
|
|
162133
162284
|
min-width: 360px;
|
|
162134
162285
|
}
|
|
162135
162286
|
|
|
162287
|
+
.min-w-\[680px\] {
|
|
162288
|
+
min-width: 680px;
|
|
162289
|
+
}
|
|
162290
|
+
|
|
162136
162291
|
.min-w-\[980px\] {
|
|
162137
162292
|
min-width: 980px;
|
|
162138
162293
|
}
|
|
@@ -162141,6 +162296,10 @@ if ("serviceWorker" in navigator) {
|
|
|
162141
162296
|
flex: 1;
|
|
162142
162297
|
}
|
|
162143
162298
|
|
|
162299
|
+
.shrink {
|
|
162300
|
+
flex-shrink: 1;
|
|
162301
|
+
}
|
|
162302
|
+
|
|
162144
162303
|
.shrink-0 {
|
|
162145
162304
|
flex-shrink: 0;
|
|
162146
162305
|
}
|
|
@@ -163913,6 +164072,10 @@ if ("serviceWorker" in navigator) {
|
|
|
163913
164072
|
padding-top: 15vh;
|
|
163914
164073
|
}
|
|
163915
164074
|
|
|
164075
|
+
.pt-\[var\(--sat\)\] {
|
|
164076
|
+
padding-top: var(--sat);
|
|
164077
|
+
}
|
|
164078
|
+
|
|
163916
164079
|
.pr-2 {
|
|
163917
164080
|
padding-right: calc(var(--spacing) * 2);
|
|
163918
164081
|
}
|
|
@@ -164515,6 +164678,10 @@ if ("serviceWorker" in navigator) {
|
|
|
164515
164678
|
-moz-osx-font-smoothing: grayscale;
|
|
164516
164679
|
}
|
|
164517
164680
|
|
|
164681
|
+
.accent-primary {
|
|
164682
|
+
accent-color: var(--primary);
|
|
164683
|
+
}
|
|
164684
|
+
|
|
164518
164685
|
.opacity-0 {
|
|
164519
164686
|
opacity: 0;
|
|
164520
164687
|
}
|
|
@@ -165730,10 +165897,18 @@ if ("serviceWorker" in navigator) {
|
|
|
165730
165897
|
flex-direction: row;
|
|
165731
165898
|
}
|
|
165732
165899
|
|
|
165900
|
+
.sm\:flex-wrap {
|
|
165901
|
+
flex-wrap: wrap;
|
|
165902
|
+
}
|
|
165903
|
+
|
|
165733
165904
|
.sm\:justify-end {
|
|
165734
165905
|
justify-content: flex-end;
|
|
165735
165906
|
}
|
|
165736
165907
|
|
|
165908
|
+
.sm\:overflow-x-visible {
|
|
165909
|
+
overflow-x: visible;
|
|
165910
|
+
}
|
|
165911
|
+
|
|
165737
165912
|
.sm\:px-2 {
|
|
165738
165913
|
padding-inline: calc(var(--spacing) * 2);
|
|
165739
165914
|
}
|
|
@@ -165742,6 +165917,10 @@ if ("serviceWorker" in navigator) {
|
|
|
165742
165917
|
padding-right: calc(var(--spacing) * 3);
|
|
165743
165918
|
}
|
|
165744
165919
|
|
|
165920
|
+
.sm\:pb-0 {
|
|
165921
|
+
padding-bottom: calc(var(--spacing) * 0);
|
|
165922
|
+
}
|
|
165923
|
+
|
|
165745
165924
|
.sm\:opacity-0 {
|
|
165746
165925
|
opacity: 0;
|
|
165747
165926
|
}
|
|
@@ -165886,10 +166065,6 @@ if ("serviceWorker" in navigator) {
|
|
|
165886
166065
|
text-wrap: pretty;
|
|
165887
166066
|
}
|
|
165888
166067
|
|
|
165889
|
-
.md\:opacity-0 {
|
|
165890
|
-
opacity: 0;
|
|
165891
|
-
}
|
|
165892
|
-
|
|
165893
166068
|
@media (hover: hover) {
|
|
165894
166069
|
.md\:group-hover\/msg\:pointer-events-auto:is(:where(.group\/msg):hover *) {
|
|
165895
166070
|
pointer-events: auto;
|
|
@@ -166500,6 +166675,20 @@ if ("serviceWorker" in navigator) {
|
|
|
166500
166675
|
height: calc(var(--spacing) * 3) !important;
|
|
166501
166676
|
}
|
|
166502
166677
|
|
|
166678
|
+
@media (hover: hover) {
|
|
166679
|
+
@media (min-width: 48rem) {
|
|
166680
|
+
.\[\@media\(hover\:hover\)\]\:md\:opacity-0 {
|
|
166681
|
+
opacity: 0;
|
|
166682
|
+
}
|
|
166683
|
+
|
|
166684
|
+
@media (hover: hover) {
|
|
166685
|
+
.\[\@media\(hover\:hover\)\]\:md\:group-hover\/msg\:opacity-100:is(:where(.group\/msg):hover *) {
|
|
166686
|
+
opacity: 1;
|
|
166687
|
+
}
|
|
166688
|
+
}
|
|
166689
|
+
}
|
|
166690
|
+
}
|
|
166691
|
+
|
|
166503
166692
|
.safe-area-bottom {
|
|
166504
166693
|
padding-bottom: max(.625rem, env(safe-area-inset-bottom));
|
|
166505
166694
|
}
|
|
@@ -167103,6 +167292,7 @@ if ("serviceWorker" in navigator) {
|
|
|
167103
167292
|
|
|
167104
167293
|
:root {
|
|
167105
167294
|
--header-h: 3rem;
|
|
167295
|
+
--sat: env(safe-area-inset-top, 0px);
|
|
167106
167296
|
}
|
|
167107
167297
|
|
|
167108
167298
|
.code-preview .shiki {
|
package/runner/src/config.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
1
2
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
3
|
import { homedir, hostname } from "node:os";
|
|
3
4
|
import { join, resolve } from "node:path";
|
|
4
5
|
import { DEFAULT_RELAY_URL, stringValue } from "agent-relay-sdk";
|
|
6
|
+
import type { WorkspaceMetadata } from "agent-relay-sdk";
|
|
5
7
|
import { sanitizeFsName } from "agent-relay-sdk/fs-name";
|
|
6
8
|
import type { ProviderConfig } from "./adapter";
|
|
7
9
|
|
|
@@ -101,6 +103,37 @@ export function runnerId(provider: string, cwd: string, label?: string): string
|
|
|
101
103
|
return `${hostname()}-${provider}-${cleanLabel}-${crypto.randomUUID().slice(0, 8)}`;
|
|
102
104
|
}
|
|
103
105
|
|
|
106
|
+
/**
|
|
107
|
+
* Stable project identifier for insights aggregation: the repo NAME, never a
|
|
108
|
+
* per-branch/per-session worktree dir or full path. Isolated workspace agents run
|
|
109
|
+
* from a session-specific worktree (…/workspaces/<repo>/<id>) whose basename is
|
|
110
|
+
* unique per session — using it would scatter one repo's data across many "projects"
|
|
111
|
+
* and break per-project rollups. So we resolve up to the main repo root, then take
|
|
112
|
+
* its basename. Falls back to the git toplevel of cwd (handles a direct agent
|
|
113
|
+
* launched in a subdir), then to the cwd basename for a non-git directory.
|
|
114
|
+
*/
|
|
115
|
+
export function resolveProjectName(cwd: string, workspace?: WorkspaceMetadata): string {
|
|
116
|
+
const root =
|
|
117
|
+
workspace?.repoRoot ||
|
|
118
|
+
workspace?.probe?.repoRoot ||
|
|
119
|
+
gitToplevel(cwd) ||
|
|
120
|
+
workspace?.sourceCwd ||
|
|
121
|
+
cwd;
|
|
122
|
+
return root.split("/").filter(Boolean).at(-1) || "unknown";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function gitToplevel(cwd: string): string | undefined {
|
|
126
|
+
try {
|
|
127
|
+
const out = execFileSync("git", ["-C", cwd, "rev-parse", "--show-toplevel"], {
|
|
128
|
+
encoding: "utf8",
|
|
129
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
130
|
+
}).trim();
|
|
131
|
+
return out || undefined;
|
|
132
|
+
} catch {
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
104
137
|
export function resolveCwd(value: string | undefined, fallback: string): string {
|
|
105
138
|
return resolve(value || fallback);
|
|
106
139
|
}
|
package/src/cli.ts
CHANGED
|
@@ -143,7 +143,8 @@ Upgrade options:
|
|
|
143
143
|
--providers LIST Provider integrations to upgrade: auto, all, codex, claude, orchestrator
|
|
144
144
|
--host ID Upgrade a remote orchestrator host over the relay (repeatable). Skips the local upgrade
|
|
145
145
|
--all-hosts Upgrade this host, then fan out to every connected remote host that is behind
|
|
146
|
-
--no-restart Do not restart agent-relay.service
|
|
146
|
+
--no-restart Do not restart agent-relay.service (warns you to restart it manually)
|
|
147
|
+
--restart-deferred Like --no-restart, but the caller restarts the services itself; suppresses the manual-restart warning (used by the release script)
|
|
147
148
|
--dry-run Print detected state and planned commands
|
|
148
149
|
--yes Skip confirmation prompts
|
|
149
150
|
|
|
@@ -402,6 +403,7 @@ async function handleUpgradeCommand(args: string[]): Promise<void> {
|
|
|
402
403
|
let targetVersion: string | undefined;
|
|
403
404
|
let dryRun = false;
|
|
404
405
|
let noRestart = false;
|
|
406
|
+
let restartDeferred = false;
|
|
405
407
|
let yes = false;
|
|
406
408
|
let json = false;
|
|
407
409
|
let runtimePrefix: string | undefined;
|
|
@@ -425,6 +427,7 @@ async function handleUpgradeCommand(args: string[]): Promise<void> {
|
|
|
425
427
|
else if (arg === "--all") providers.push("all");
|
|
426
428
|
else if (arg === "--dry-run") dryRun = true;
|
|
427
429
|
else if (arg === "--no-restart") noRestart = true;
|
|
430
|
+
else if (arg === "--restart-deferred") restartDeferred = true;
|
|
428
431
|
else if (arg === "--yes" || arg === "-y") yes = true;
|
|
429
432
|
else if (arg === "--json") json = true;
|
|
430
433
|
else throw new Error(`Unknown upgrade option "${arg}"`);
|
|
@@ -449,6 +452,7 @@ async function handleUpgradeCommand(args: string[]): Promise<void> {
|
|
|
449
452
|
...(runtimePrefix ? { runtimePrefix } : {}),
|
|
450
453
|
providers,
|
|
451
454
|
noRestart,
|
|
455
|
+
restartDeferred,
|
|
452
456
|
});
|
|
453
457
|
|
|
454
458
|
if (json) {
|
|
@@ -2034,7 +2038,7 @@ async function handleIntrospectCommand(args: string[]): Promise<void> {
|
|
|
2034
2038
|
|
|
2035
2039
|
const observation = await apiRequest("POST", "/api/insights/observations", {
|
|
2036
2040
|
sessionId: sessionId || process.env.AGENT_RELAY_PROVIDER_SESSION_ID || `manual-${from}`,
|
|
2037
|
-
project: project || process.cwd(),
|
|
2041
|
+
project: project || process.env.AGENT_RELAY_PROJECT || process.cwd().split("/").filter(Boolean).at(-1) || process.cwd(),
|
|
2038
2042
|
agentId: from,
|
|
2039
2043
|
signal: "introspection",
|
|
2040
2044
|
source: "agent",
|
package/src/upgrade.ts
CHANGED
|
@@ -13,6 +13,13 @@ type UpgradeOptions = {
|
|
|
13
13
|
targetVersion?: string;
|
|
14
14
|
providers?: UpgradeProvider[];
|
|
15
15
|
noRestart?: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* Like `noRestart` (no restart action → no premature post-restart verify),
|
|
18
|
+
* but the caller restarts the services itself right after install (the
|
|
19
|
+
* release script does). Suppresses the "restart manually" warning, which is
|
|
20
|
+
* a false alarm in that flow — only the caller knows the restart is coming.
|
|
21
|
+
*/
|
|
22
|
+
restartDeferred?: boolean;
|
|
16
23
|
runtimePrefix?: string;
|
|
17
24
|
};
|
|
18
25
|
|
|
@@ -206,10 +213,14 @@ export function createUpgradePlan(snapshot: UpgradeSnapshot, options: UpgradeOpt
|
|
|
206
213
|
warnings.push("Agent Relay orchestrator not detected; skipping orchestrator package upgrade.");
|
|
207
214
|
}
|
|
208
215
|
|
|
216
|
+
// `restartDeferred` means a caller restarts for us — skip the restart action
|
|
217
|
+
// (and thus the post-restart verify) exactly like `noRestart`, but without the
|
|
218
|
+
// "restart manually" warning, which would be a false alarm in that flow.
|
|
219
|
+
const deferRestart = Boolean(options.noRestart || options.restartDeferred);
|
|
209
220
|
const serverRestartNeeded = serverPackageUpdated || Boolean(snapshot.runningServerVersion && snapshot.runningServerVersion !== targetVersion);
|
|
210
221
|
if (snapshot.hasSystemdUserService && serverRestartNeeded) {
|
|
211
|
-
if (
|
|
212
|
-
warnings.push("agent-relay.service detected but --no-restart was set; restart manually to run the upgraded server.");
|
|
222
|
+
if (deferRestart) {
|
|
223
|
+
if (!options.restartDeferred) warnings.push("agent-relay.service detected but --no-restart was set; restart manually to run the upgraded server.");
|
|
213
224
|
} else {
|
|
214
225
|
actions.push({
|
|
215
226
|
label: "Restart Agent Relay service",
|
|
@@ -226,8 +237,8 @@ export function createUpgradePlan(snapshot: UpgradeSnapshot, options: UpgradeOpt
|
|
|
226
237
|
Boolean((orch.version && orch.version !== targetVersion) || orch.health?.restartRequired)
|
|
227
238
|
);
|
|
228
239
|
if (snapshot.hasSystemdUserOrchestratorService && orchestratorRestartNeeded) {
|
|
229
|
-
if (
|
|
230
|
-
warnings.push("agent-relay-orchestrator.service detected but --no-restart was set; restart manually to run the upgraded orchestrator.");
|
|
240
|
+
if (deferRestart) {
|
|
241
|
+
if (!options.restartDeferred) warnings.push("agent-relay-orchestrator.service detected but --no-restart was set; restart manually to run the upgraded orchestrator.");
|
|
231
242
|
} else {
|
|
232
243
|
actions.push({
|
|
233
244
|
label: "Restart Agent Relay orchestrator service",
|
package/src/workspace-phase.ts
CHANGED
|
@@ -232,9 +232,12 @@ function metaNumber(meta: Record<string, unknown> | undefined, key: string): num
|
|
|
232
232
|
//
|
|
233
233
|
// idle/changes need the worktree's ahead/dirty counts, which the relay isn't in the
|
|
234
234
|
// git path to know live — the ~2 min conflict scan stashes them in metadata
|
|
235
|
-
// (`gitAhead`/`gitDirtyCount`). Until the first scan they're absent
|
|
236
|
-
//
|
|
237
|
-
//
|
|
235
|
+
// (`gitAhead`/`gitDirtyCount`). Until the first scan they're absent: presume `idle`,
|
|
236
|
+
// because a freshly-spawned (or freshly-rebased post-land `--N`) worktree genuinely
|
|
237
|
+
// has nothing ahead/dirty, and the old optimistic `changes` default mislabeled every
|
|
238
|
+
// brand-new branch as having unlanded work (#236 v2). The scan flips it to `changes`
|
|
239
|
+
// within one sweep once real commits/dirt appear. The "mark ready" affordance no
|
|
240
|
+
// longer depends on this guess — the dashboard offers it on idle too.
|
|
238
241
|
//
|
|
239
242
|
// Returns undefined for non-branch / torn-down workspaces (no badge).
|
|
240
243
|
export function deriveBranchState(
|
|
@@ -248,7 +251,8 @@ export function deriveBranchState(
|
|
|
248
251
|
const meta = workspace.metadata as Record<string, unknown> | undefined;
|
|
249
252
|
const ahead = metaNumber(meta, "gitAhead");
|
|
250
253
|
const dirty = metaNumber(meta, "gitDirtyCount");
|
|
251
|
-
|
|
254
|
+
// Unprobed → presume idle (clean fresh/rebased branch); the scan reveals real work.
|
|
255
|
+
if (ahead === undefined && dirty === undefined) return "idle";
|
|
252
256
|
return (ahead ?? 0) > 0 || (dirty ?? 0) > 0 ? "changes" : "idle";
|
|
253
257
|
}
|
|
254
258
|
case "ready":
|