agent-relay-server 0.30.1 → 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 +237 -69
- package/runner/src/config.ts +33 -0
- package/src/cli.ts +1 -1
- 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;
|
|
@@ -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,18 +126533,7 @@ function TerminalViewer({ orchestratorId, session, interactive: initialInteracti
|
|
|
126441
126533
|
});
|
|
126442
126534
|
}
|
|
126443
126535
|
function TerminalDialog({ open, onOpenChange, orchestratorId, session, interactive }) {
|
|
126444
|
-
const
|
|
126445
|
-
(0, import_react.useEffect)(() => {
|
|
126446
|
-
if (!open) return;
|
|
126447
|
-
const vv = window.visualViewport;
|
|
126448
|
-
if (!vv) return;
|
|
126449
|
-
function onResize() {
|
|
126450
|
-
setVpHeight(vv.height);
|
|
126451
|
-
}
|
|
126452
|
-
onResize();
|
|
126453
|
-
vv.addEventListener("resize", onResize);
|
|
126454
|
-
return () => vv.removeEventListener("resize", onResize);
|
|
126455
|
-
}, [open]);
|
|
126536
|
+
const vpHeight = useKeyboardViewportHeight(open);
|
|
126456
126537
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Dialog, {
|
|
126457
126538
|
open,
|
|
126458
126539
|
onOpenChange,
|
|
@@ -128907,7 +128988,7 @@ var MessageBubble = (0, import_react.memo)(function MessageBubble({ msg, peer, o
|
|
|
128907
128988
|
type: "button",
|
|
128908
128989
|
title: isTtsPlaying ? "Stop playback" : "Play aloud",
|
|
128909
128990
|
onClick: togglePlay,
|
|
128910
|
-
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"),
|
|
128911
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" })
|
|
128912
128993
|
}),
|
|
128913
128994
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
@@ -129705,7 +129786,7 @@ function ChatPanel({ threads, onBack, showBackButton }) {
|
|
|
129705
129786
|
onClick: interruptAgent,
|
|
129706
129787
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CircleStop, { className: "w-3.5 h-3.5" })
|
|
129707
129788
|
}),
|
|
129708
|
-
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, {
|
|
129709
129790
|
variant: "ghost",
|
|
129710
129791
|
size: "icon-sm",
|
|
129711
129792
|
className: "hidden @4xl/chat:inline-flex text-amber-400 hover:text-amber-300",
|
|
@@ -129800,7 +129881,7 @@ function ChatPanel({ threads, onBack, showBackButton }) {
|
|
|
129800
129881
|
onClick: interruptAgent,
|
|
129801
129882
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(CircleStop, { className: "w-3.5 h-3.5" }), "Interrupt"]
|
|
129802
129883
|
}),
|
|
129803
|
-
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, {
|
|
129804
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")),
|
|
129805
129886
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Flag, { className: "w-3.5 h-3.5" }), "Mark Ready"]
|
|
129806
129887
|
}),
|
|
@@ -130150,17 +130231,7 @@ function ChatView() {
|
|
|
130150
130231
|
});
|
|
130151
130232
|
}
|
|
130152
130233
|
function MobileChatContainer({ children }) {
|
|
130153
|
-
const
|
|
130154
|
-
(0, import_react.useEffect)(() => {
|
|
130155
|
-
const vv = window.visualViewport;
|
|
130156
|
-
if (!vv) return;
|
|
130157
|
-
function onResize() {
|
|
130158
|
-
setHeight(vv.height);
|
|
130159
|
-
}
|
|
130160
|
-
onResize();
|
|
130161
|
-
vv.addEventListener("resize", onResize);
|
|
130162
|
-
return () => vv.removeEventListener("resize", onResize);
|
|
130163
|
-
}, []);
|
|
130234
|
+
const height = useKeyboardViewportHeight();
|
|
130164
130235
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
130165
130236
|
className: "fixed inset-x-0 top-0 z-40 flex flex-col bg-background pt-[var(--sat)]",
|
|
130166
130237
|
style: { height: height ? `${height}px` : "100dvh" },
|
|
@@ -130326,7 +130397,7 @@ function AgentCard({ agent }) {
|
|
|
130326
130397
|
onClick: () => void handleOpenTerminal(),
|
|
130327
130398
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Terminal, { className: "w-3 h-3" })
|
|
130328
130399
|
}),
|
|
130329
|
-
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, {
|
|
130330
130401
|
size: "icon",
|
|
130331
130402
|
variant: "ghost",
|
|
130332
130403
|
className: "h-7 w-7 text-amber-400 hover:text-amber-300",
|
|
@@ -155609,7 +155680,7 @@ function InsightsView() {
|
|
|
155609
155680
|
}) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
|
|
155610
155681
|
className: "overflow-x-auto",
|
|
155611
155682
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("table", {
|
|
155612
|
-
className: "w-full text-left text-xs",
|
|
155683
|
+
className: "w-full min-w-[680px] text-left text-xs",
|
|
155613
155684
|
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("thead", {
|
|
155614
155685
|
className: "text-muted-foreground",
|
|
155615
155686
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("tr", {
|
|
@@ -159369,15 +159440,23 @@ function OrchestratorSpawnModal() {
|
|
|
159369
159440
|
const workspaceMode = useRelayStore((s) => s.spawnWorkspaceMode);
|
|
159370
159441
|
const prompt = useRelayStore((s) => s.spawnPrompt);
|
|
159371
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));
|
|
159372
159447
|
const set = useRelayStore((s) => s.set);
|
|
159373
159448
|
const submitOrchestratorSpawn = useRelayStore((s) => s.submitOrchestratorSpawn);
|
|
159449
|
+
const selectSpawnHistory = useRelayStore((s) => s.selectSpawnHistory);
|
|
159374
159450
|
const agentProfiles = useRelayStore((s) => s.agentProfiles);
|
|
159375
159451
|
const orch = orchestrators.find((o) => o.id === orchId);
|
|
159376
159452
|
const providers = orch?.providers || [];
|
|
159377
159453
|
const models = PROVIDER_CATALOG[provider]?.models || [];
|
|
159378
159454
|
const selectedModel = models.find((m) => m.alias === model);
|
|
159379
159455
|
const efforts = selectedModel?.efforts || [];
|
|
159380
|
-
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);
|
|
159381
159460
|
function selectOrchestrator(nextId) {
|
|
159382
159461
|
const nextOrch = orchestrators.find((o) => o.id === nextId);
|
|
159383
159462
|
const nextProvider = nextOrch?.providers[0] || "";
|
|
@@ -159407,6 +159486,7 @@ function OrchestratorSpawnModal() {
|
|
|
159407
159486
|
onOpenChange: (o) => !o && set({ orchestratorSpawnOpen: false }),
|
|
159408
159487
|
children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogContent, {
|
|
159409
159488
|
className: "max-w-lg max-h-[90dvh] flex flex-col",
|
|
159489
|
+
onOpenAutoFocus: (e) => e.preventDefault(),
|
|
159410
159490
|
children: [
|
|
159411
159491
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogHeader, { children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DialogTitle, { children: "Spawn Agent" }) }),
|
|
159412
159492
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
@@ -159515,15 +159595,39 @@ function OrchestratorSpawnModal() {
|
|
|
159515
159595
|
})
|
|
159516
159596
|
]
|
|
159517
159597
|
})] }),
|
|
159518
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
159519
|
-
|
|
159520
|
-
|
|
159521
|
-
|
|
159522
|
-
|
|
159523
|
-
|
|
159524
|
-
|
|
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
|
+
})]
|
|
159525
159629
|
})
|
|
159526
|
-
|
|
159630
|
+
] }),
|
|
159527
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, {
|
|
159528
159632
|
value: label,
|
|
159529
159633
|
onChange: (e) => set({ spawnLabel: e.target.value }),
|
|
@@ -159554,17 +159658,47 @@ function OrchestratorSpawnModal() {
|
|
|
159554
159658
|
className: "text-xs text-muted-foreground mt-1",
|
|
159555
159659
|
children: "Use for fork history or launch context. It informs the session but does not start work by itself."
|
|
159556
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
|
+
})
|
|
159557
159690
|
] })
|
|
159558
159691
|
]
|
|
159559
159692
|
}),
|
|
159560
159693
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(DialogFooter, { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
159561
159694
|
variant: "outline",
|
|
159562
159695
|
onClick: () => set({ orchestratorSpawnOpen: false }),
|
|
159696
|
+
disabled: isSpawning,
|
|
159563
159697
|
children: "Cancel"
|
|
159564
159698
|
}), /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Button, {
|
|
159565
159699
|
onClick: submitOrchestratorSpawn,
|
|
159566
159700
|
disabled: !canSpawn,
|
|
159567
|
-
children: providers.length ? "Spawn" : "No providers"
|
|
159701
|
+
children: isSpawning ? "Spawning…" : providers.length ? count > 1 ? `Spawn ${count}` : "Spawn" : "No providers"
|
|
159568
159702
|
})] })
|
|
159569
159703
|
]
|
|
159570
159704
|
})
|
|
@@ -160695,7 +160829,7 @@ function App() {
|
|
|
160695
160829
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Sidebar, {}),
|
|
160696
160830
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MobileDrawer, {}),
|
|
160697
160831
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("main", {
|
|
160698
|
-
className: "flex-1 h-[calc(100dvh-var(--sat))] overflow-y-auto",
|
|
160832
|
+
className: "flex-1 min-w-0 h-[calc(100dvh-var(--sat))] overflow-y-auto",
|
|
160699
160833
|
children: [
|
|
160700
160834
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(MobileNav, {}),
|
|
160701
160835
|
!authNeeded && connectionError && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
@@ -162150,6 +162284,10 @@ if ("serviceWorker" in navigator) {
|
|
|
162150
162284
|
min-width: 360px;
|
|
162151
162285
|
}
|
|
162152
162286
|
|
|
162287
|
+
.min-w-\[680px\] {
|
|
162288
|
+
min-width: 680px;
|
|
162289
|
+
}
|
|
162290
|
+
|
|
162153
162291
|
.min-w-\[980px\] {
|
|
162154
162292
|
min-width: 980px;
|
|
162155
162293
|
}
|
|
@@ -162158,6 +162296,10 @@ if ("serviceWorker" in navigator) {
|
|
|
162158
162296
|
flex: 1;
|
|
162159
162297
|
}
|
|
162160
162298
|
|
|
162299
|
+
.shrink {
|
|
162300
|
+
flex-shrink: 1;
|
|
162301
|
+
}
|
|
162302
|
+
|
|
162161
162303
|
.shrink-0 {
|
|
162162
162304
|
flex-shrink: 0;
|
|
162163
162305
|
}
|
|
@@ -164536,6 +164678,10 @@ if ("serviceWorker" in navigator) {
|
|
|
164536
164678
|
-moz-osx-font-smoothing: grayscale;
|
|
164537
164679
|
}
|
|
164538
164680
|
|
|
164681
|
+
.accent-primary {
|
|
164682
|
+
accent-color: var(--primary);
|
|
164683
|
+
}
|
|
164684
|
+
|
|
164539
164685
|
.opacity-0 {
|
|
164540
164686
|
opacity: 0;
|
|
164541
164687
|
}
|
|
@@ -165751,10 +165897,18 @@ if ("serviceWorker" in navigator) {
|
|
|
165751
165897
|
flex-direction: row;
|
|
165752
165898
|
}
|
|
165753
165899
|
|
|
165900
|
+
.sm\:flex-wrap {
|
|
165901
|
+
flex-wrap: wrap;
|
|
165902
|
+
}
|
|
165903
|
+
|
|
165754
165904
|
.sm\:justify-end {
|
|
165755
165905
|
justify-content: flex-end;
|
|
165756
165906
|
}
|
|
165757
165907
|
|
|
165908
|
+
.sm\:overflow-x-visible {
|
|
165909
|
+
overflow-x: visible;
|
|
165910
|
+
}
|
|
165911
|
+
|
|
165758
165912
|
.sm\:px-2 {
|
|
165759
165913
|
padding-inline: calc(var(--spacing) * 2);
|
|
165760
165914
|
}
|
|
@@ -165763,6 +165917,10 @@ if ("serviceWorker" in navigator) {
|
|
|
165763
165917
|
padding-right: calc(var(--spacing) * 3);
|
|
165764
165918
|
}
|
|
165765
165919
|
|
|
165920
|
+
.sm\:pb-0 {
|
|
165921
|
+
padding-bottom: calc(var(--spacing) * 0);
|
|
165922
|
+
}
|
|
165923
|
+
|
|
165766
165924
|
.sm\:opacity-0 {
|
|
165767
165925
|
opacity: 0;
|
|
165768
165926
|
}
|
|
@@ -165907,10 +166065,6 @@ if ("serviceWorker" in navigator) {
|
|
|
165907
166065
|
text-wrap: pretty;
|
|
165908
166066
|
}
|
|
165909
166067
|
|
|
165910
|
-
.md\:opacity-0 {
|
|
165911
|
-
opacity: 0;
|
|
165912
|
-
}
|
|
165913
|
-
|
|
165914
166068
|
@media (hover: hover) {
|
|
165915
166069
|
.md\:group-hover\/msg\:pointer-events-auto:is(:where(.group\/msg):hover *) {
|
|
165916
166070
|
pointer-events: auto;
|
|
@@ -166521,6 +166675,20 @@ if ("serviceWorker" in navigator) {
|
|
|
166521
166675
|
height: calc(var(--spacing) * 3) !important;
|
|
166522
166676
|
}
|
|
166523
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
|
+
|
|
166524
166692
|
.safe-area-bottom {
|
|
166525
166693
|
padding-bottom: max(.625rem, env(safe-area-inset-bottom));
|
|
166526
166694
|
}
|
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
|
@@ -2038,7 +2038,7 @@ async function handleIntrospectCommand(args: string[]): Promise<void> {
|
|
|
2038
2038
|
|
|
2039
2039
|
const observation = await apiRequest("POST", "/api/insights/observations", {
|
|
2040
2040
|
sessionId: sessionId || process.env.AGENT_RELAY_PROVIDER_SESSION_ID || `manual-${from}`,
|
|
2041
|
-
project: project || process.cwd(),
|
|
2041
|
+
project: project || process.env.AGENT_RELAY_PROJECT || process.cwd().split("/").filter(Boolean).at(-1) || process.cwd(),
|
|
2042
2042
|
agentId: from,
|
|
2043
2043
|
signal: "introspection",
|
|
2044
2044
|
source: "agent",
|
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":
|