agent-relay-server 0.17.0 → 0.19.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/docs/openapi.json +101 -1
- package/package.json +2 -2
- package/public/index.html +39 -32
- package/public/sw.js +51 -16
- package/runner/src/adapter.ts +1 -4
- package/runner/src/config.ts +3 -5
- package/scripts/orchestrator-spawn-smoke.ts +2 -1
- package/src/automations.ts +20 -59
- package/src/bus.ts +3 -18
- package/src/cli.ts +244 -7
- package/src/command-events.ts +26 -0
- package/src/config-store.ts +12 -47
- package/src/connectors.ts +1 -4
- package/src/contracts.ts +2 -8
- package/src/daemon.ts +1 -4
- package/src/db.ts +23 -17
- package/src/dev.ts +1 -4
- package/src/http-body.ts +49 -0
- package/src/index.ts +101 -5
- package/src/lifecycle-manager.ts +11 -24
- package/src/maintenance.ts +28 -22
- package/src/managed-policy.ts +9 -28
- package/src/mcp.ts +35 -110
- package/src/memory-broker-smoke.ts +4 -2
- package/src/memory-command-broker.ts +2 -5
- package/src/memory-http-broker.ts +2 -5
- package/src/memory-service.ts +1 -4
- package/src/memory-sqlite-broker.ts +1 -8
- package/src/orchestrator-lookup.ts +29 -0
- package/src/provider-catalog-store.ts +3 -11
- package/src/recipe-loader.ts +1 -4
- package/src/recipe-validator.ts +2 -5
- package/src/routes.ts +417 -309
- package/src/security.ts +3 -7
- package/src/setup.ts +1 -4
- package/src/spawn-command.ts +151 -0
- package/src/sse.ts +1 -4
- package/src/steward.ts +17 -21
- package/src/upgrade.ts +40 -13
- package/src/utils.ts +38 -0
- package/src/validation.ts +80 -0
- package/src/workspace-claim.ts +29 -0
- package/src/workspace-merge.ts +21 -9
package/docs/openapi.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"openapi": "3.1.0",
|
|
3
3
|
"info": {
|
|
4
4
|
"title": "Agent Relay API",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.17.0",
|
|
6
6
|
"description": "Real-time message bus for inter-agent communication. Agent-first: this spec is designed for machine consumption — agents can self-discover the full API surface via GET /api/spec.",
|
|
7
7
|
"license": {
|
|
8
8
|
"name": "MIT",
|
|
@@ -3818,6 +3818,58 @@
|
|
|
3818
3818
|
]
|
|
3819
3819
|
}
|
|
3820
3820
|
},
|
|
3821
|
+
"/api/workspaces/actions/cleanup-stale": {
|
|
3822
|
+
"post": {
|
|
3823
|
+
"operationId": "postWorkspaceCleanupStale",
|
|
3824
|
+
"summary": "Post Workspace Cleanup Stale",
|
|
3825
|
+
"tags": [
|
|
3826
|
+
"Other"
|
|
3827
|
+
],
|
|
3828
|
+
"requestBody": {
|
|
3829
|
+
"required": true,
|
|
3830
|
+
"content": {
|
|
3831
|
+
"application/json": {
|
|
3832
|
+
"schema": {
|
|
3833
|
+
"type": "object",
|
|
3834
|
+
"properties": {
|
|
3835
|
+
"repoRoot": {
|
|
3836
|
+
"type": "string"
|
|
3837
|
+
},
|
|
3838
|
+
"dryRun": {
|
|
3839
|
+
"type": "string"
|
|
3840
|
+
},
|
|
3841
|
+
"landedOnly": {
|
|
3842
|
+
"type": "string"
|
|
3843
|
+
},
|
|
3844
|
+
"offlineOwnerOnly": {
|
|
3845
|
+
"type": "string"
|
|
3846
|
+
}
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3849
|
+
}
|
|
3850
|
+
}
|
|
3851
|
+
},
|
|
3852
|
+
"responses": {
|
|
3853
|
+
"200": {
|
|
3854
|
+
"description": "Success",
|
|
3855
|
+
"content": {
|
|
3856
|
+
"application/json": {}
|
|
3857
|
+
}
|
|
3858
|
+
}
|
|
3859
|
+
},
|
|
3860
|
+
"security": [
|
|
3861
|
+
{
|
|
3862
|
+
"bearerAuth": []
|
|
3863
|
+
},
|
|
3864
|
+
{
|
|
3865
|
+
"tokenHeader": []
|
|
3866
|
+
},
|
|
3867
|
+
{
|
|
3868
|
+
"tokenQuery": []
|
|
3869
|
+
}
|
|
3870
|
+
]
|
|
3871
|
+
}
|
|
3872
|
+
},
|
|
3821
3873
|
"/api/workspaces/{id}": {
|
|
3822
3874
|
"get": {
|
|
3823
3875
|
"operationId": "getWorkspaceById",
|
|
@@ -4005,6 +4057,54 @@
|
|
|
4005
4057
|
]
|
|
4006
4058
|
}
|
|
4007
4059
|
},
|
|
4060
|
+
"/api/workspaces/{id}/diagnostics": {
|
|
4061
|
+
"get": {
|
|
4062
|
+
"operationId": "getWorkspaceDiagnostics",
|
|
4063
|
+
"summary": "Get Workspace Diagnostics",
|
|
4064
|
+
"tags": [
|
|
4065
|
+
"Other"
|
|
4066
|
+
],
|
|
4067
|
+
"parameters": [
|
|
4068
|
+
{
|
|
4069
|
+
"name": "id",
|
|
4070
|
+
"in": "path",
|
|
4071
|
+
"required": true,
|
|
4072
|
+
"schema": {
|
|
4073
|
+
"type": "string"
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
4076
|
+
],
|
|
4077
|
+
"responses": {
|
|
4078
|
+
"200": {
|
|
4079
|
+
"description": "Success",
|
|
4080
|
+
"content": {
|
|
4081
|
+
"application/json": {}
|
|
4082
|
+
}
|
|
4083
|
+
},
|
|
4084
|
+
"404": {
|
|
4085
|
+
"description": "Not found",
|
|
4086
|
+
"content": {
|
|
4087
|
+
"application/json": {
|
|
4088
|
+
"schema": {
|
|
4089
|
+
"$ref": "#/components/schemas/Error"
|
|
4090
|
+
}
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
4093
|
+
}
|
|
4094
|
+
},
|
|
4095
|
+
"security": [
|
|
4096
|
+
{
|
|
4097
|
+
"bearerAuth": []
|
|
4098
|
+
},
|
|
4099
|
+
{
|
|
4100
|
+
"tokenHeader": []
|
|
4101
|
+
},
|
|
4102
|
+
{
|
|
4103
|
+
"tokenQuery": []
|
|
4104
|
+
}
|
|
4105
|
+
]
|
|
4106
|
+
}
|
|
4107
|
+
},
|
|
4008
4108
|
"/api/workspaces/{id}/diff": {
|
|
4009
4109
|
"get": {
|
|
4010
4110
|
"operationId": "getWorkspaceDiff",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-relay-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.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.
|
|
36
|
+
"agent-relay-sdk": "0.2.10"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"prepack": "bun run build:dashboard:bundle >&2",
|
package/public/index.html
CHANGED
|
@@ -10816,6 +10816,25 @@ var PAIR_STATUS_COLORS = {
|
|
|
10816
10816
|
expired: "bg-red-500/20 text-red-400"
|
|
10817
10817
|
};
|
|
10818
10818
|
//#endregion
|
|
10819
|
+
//#region ../sdk/src/types.ts
|
|
10820
|
+
/** True for a non-null, non-array object. The canonical type guard for the whole repo. */
|
|
10821
|
+
function isRecord(value) {
|
|
10822
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
10823
|
+
}
|
|
10824
|
+
/**
|
|
10825
|
+
* Narrow `unknown` to a non-empty trimmed string, else `undefined`.
|
|
10826
|
+
* Settled semantics: whitespace-only is treated as empty (returns `undefined`).
|
|
10827
|
+
*/
|
|
10828
|
+
function stringValue(value) {
|
|
10829
|
+
if (typeof value !== "string") return void 0;
|
|
10830
|
+
const trimmed = value.trim();
|
|
10831
|
+
return trimmed.length > 0 ? trimmed : void 0;
|
|
10832
|
+
}
|
|
10833
|
+
/** Extract a human-readable message from any thrown value. */
|
|
10834
|
+
function errMessage(error) {
|
|
10835
|
+
return error instanceof Error ? error.message : String(error);
|
|
10836
|
+
}
|
|
10837
|
+
//#endregion
|
|
10819
10838
|
//#region src/lib/display.ts
|
|
10820
10839
|
function toTimestamp(value) {
|
|
10821
10840
|
const ts = typeof value === "number" ? value : new Date(value || 0).getTime();
|
|
@@ -10901,7 +10920,7 @@ function isAgentStale(now, agent) {
|
|
|
10901
10920
|
if (!agent?.lastSeen || agent.status === "offline") return false;
|
|
10902
10921
|
if (agent.id === "user" || agent.id === "system") return false;
|
|
10903
10922
|
const transport = agent.meta?.transport;
|
|
10904
|
-
if (isRecord
|
|
10923
|
+
if (isRecord(transport) && transport.connected === true) return false;
|
|
10905
10924
|
const lastSeenMs = new Date(agent.lastSeen).getTime();
|
|
10906
10925
|
if (!Number.isFinite(lastSeenMs)) return false;
|
|
10907
10926
|
return now - lastSeenMs > 6e4;
|
|
@@ -11060,7 +11079,7 @@ function messageMatchesChannel(message, channel) {
|
|
|
11060
11079
|
}
|
|
11061
11080
|
function providerRuntimeState(agent) {
|
|
11062
11081
|
const raw = agent?.meta?.providerState;
|
|
11063
|
-
if (!isRecord
|
|
11082
|
+
if (!isRecord(raw) || typeof raw.state !== "string") return null;
|
|
11064
11083
|
return {
|
|
11065
11084
|
state: raw.state,
|
|
11066
11085
|
reason: typeof raw.reason === "string" ? raw.reason : void 0,
|
|
@@ -11073,16 +11092,16 @@ function providerRuntimeState(agent) {
|
|
|
11073
11092
|
};
|
|
11074
11093
|
}
|
|
11075
11094
|
function providerPendingApproval(value) {
|
|
11076
|
-
if (!isRecord
|
|
11077
|
-
const choices = Array.isArray(value.choices) ? value.choices.filter(isRecord
|
|
11095
|
+
if (!isRecord(value) || typeof value.id !== "string") return void 0;
|
|
11096
|
+
const choices = Array.isArray(value.choices) ? value.choices.filter(isRecord).map((choice) => ({
|
|
11078
11097
|
id: choice.id,
|
|
11079
11098
|
label: typeof choice.label === "string" ? choice.label : String(choice.id || "")
|
|
11080
11099
|
})).filter((choice) => (choice.id === "approve" || choice.id === "approve-session" || choice.id === "deny" || choice.id === "abort") && Boolean(choice.label)) : [];
|
|
11081
|
-
const questions = Array.isArray(value.questions) ? value.questions.filter(isRecord
|
|
11100
|
+
const questions = Array.isArray(value.questions) ? value.questions.filter(isRecord).map((q) => ({
|
|
11082
11101
|
question: typeof q.question === "string" ? q.question : "",
|
|
11083
11102
|
header: typeof q.header === "string" ? q.header : void 0,
|
|
11084
11103
|
multiSelect: q.multiSelect === true,
|
|
11085
|
-
options: Array.isArray(q.options) ? q.options.filter(isRecord
|
|
11104
|
+
options: Array.isArray(q.options) ? q.options.filter(isRecord).map((o) => ({
|
|
11086
11105
|
label: typeof o.label === "string" ? o.label : String(o.label ?? ""),
|
|
11087
11106
|
description: typeof o.description === "string" ? o.description : void 0
|
|
11088
11107
|
})).filter((o) => Boolean(o.label)) : []
|
|
@@ -11110,7 +11129,7 @@ function activeSubagents(agent) {
|
|
|
11110
11129
|
label: `Subagent ${index + 1}`
|
|
11111
11130
|
})) : [];
|
|
11112
11131
|
}
|
|
11113
|
-
return raw.filter(isRecord
|
|
11132
|
+
return raw.filter(isRecord).map((item, index) => {
|
|
11114
11133
|
const id = typeof item.id === "string" && item.id ? item.id : `subagent-${index + 1}`;
|
|
11115
11134
|
const role = typeof item.role === "string" && item.role ? item.role : void 0;
|
|
11116
11135
|
return {
|
|
@@ -11256,9 +11275,6 @@ function presenceBadges(agent, attention, pair) {
|
|
|
11256
11275
|
});
|
|
11257
11276
|
return badges;
|
|
11258
11277
|
}
|
|
11259
|
-
function isRecord$2(value) {
|
|
11260
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11261
|
-
}
|
|
11262
11278
|
function emptyAttention() {
|
|
11263
11279
|
return {
|
|
11264
11280
|
unread: 0,
|
|
@@ -11525,7 +11541,7 @@ function downloadText(filename, text, type) {
|
|
|
11525
11541
|
URL.revokeObjectURL(url);
|
|
11526
11542
|
}
|
|
11527
11543
|
//#endregion
|
|
11528
|
-
//#region ../sdk/
|
|
11544
|
+
//#region ../sdk/src/provider-catalog.ts
|
|
11529
11545
|
var CLAUDE_LOW_TO_MAX = [
|
|
11530
11546
|
"low",
|
|
11531
11547
|
"medium",
|
|
@@ -108614,9 +108630,6 @@ function workspaceMode(agent) {
|
|
|
108614
108630
|
function recordValue(value) {
|
|
108615
108631
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
108616
108632
|
}
|
|
108617
|
-
function stringValue(value) {
|
|
108618
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
108619
|
-
}
|
|
108620
108633
|
function runtimeBadges(agent) {
|
|
108621
108634
|
const caps = agent.providerCapabilities;
|
|
108622
108635
|
const context = agent.context;
|
|
@@ -123768,7 +123781,7 @@ function TerminalViewer({ orchestratorId, session, interactive: initialInteracti
|
|
|
123768
123781
|
try {
|
|
123769
123782
|
writeSnapshot(await apiCall("GET", `/orchestrators/${encodeURIComponent(orchestratorId)}/terminal/${encodeURIComponent(session)}`));
|
|
123770
123783
|
} catch (e) {
|
|
123771
|
-
setError(
|
|
123784
|
+
setError(errMessage(e));
|
|
123772
123785
|
} finally {
|
|
123773
123786
|
inFlightRef.current = false;
|
|
123774
123787
|
}
|
|
@@ -123825,7 +123838,7 @@ function TerminalViewer({ orchestratorId, session, interactive: initialInteracti
|
|
|
123825
123838
|
for (const chunk of chunks) inputQueueRef.current = inputQueueRef.current.then(() => apiCall("POST", `/orchestrators/${encodeURIComponent(orchestratorId)}/terminal/${encodeURIComponent(session)}/input`, { data: chunk })).then(() => {
|
|
123826
123839
|
setError(null);
|
|
123827
123840
|
}).catch((e) => {
|
|
123828
|
-
setError(
|
|
123841
|
+
setError(errMessage(e));
|
|
123829
123842
|
});
|
|
123830
123843
|
}
|
|
123831
123844
|
function handleTerminalData(data) {
|
|
@@ -124420,7 +124433,7 @@ function useFileRead(orchestratorId, selectedPath) {
|
|
|
124420
124433
|
}).catch((e) => {
|
|
124421
124434
|
if (!cancelled) {
|
|
124422
124435
|
setFile(null);
|
|
124423
|
-
setError(
|
|
124436
|
+
setError(errMessage(e));
|
|
124424
124437
|
}
|
|
124425
124438
|
}).finally(() => {
|
|
124426
124439
|
if (!cancelled) setLoading(false);
|
|
@@ -124458,7 +124471,7 @@ function useFileListing(orchestratorId, selectedPath, git = false) {
|
|
|
124458
124471
|
}).catch((e) => {
|
|
124459
124472
|
if (!cancelled) {
|
|
124460
124473
|
setListing(null);
|
|
124461
|
-
setError(
|
|
124474
|
+
setError(errMessage(e));
|
|
124462
124475
|
}
|
|
124463
124476
|
}).finally(() => {
|
|
124464
124477
|
if (!cancelled) setLoading(false);
|
|
@@ -124774,7 +124787,7 @@ function FileBrowser({ overlay = false }) {
|
|
|
124774
124787
|
setPathDraft(result.path);
|
|
124775
124788
|
setReadError("");
|
|
124776
124789
|
} catch (e) {
|
|
124777
|
-
setError(
|
|
124790
|
+
setError(errMessage(e));
|
|
124778
124791
|
} finally {
|
|
124779
124792
|
setLoading(false);
|
|
124780
124793
|
}
|
|
@@ -128115,7 +128128,7 @@ function LogViewer({ orchestratorId, session, lines = 200 }) {
|
|
|
128115
128128
|
setLogLines((await apiCall("GET", `/orchestrators/${encodeURIComponent(orchestratorId)}/logs/${encodeURIComponent(session)}?lines=${lines}${stream === "mirror" ? "&stream=mirror" : ""}`)).lines || []);
|
|
128116
128129
|
setError(null);
|
|
128117
128130
|
} catch (e) {
|
|
128118
|
-
setError(
|
|
128131
|
+
setError(errMessage(e));
|
|
128119
128132
|
}
|
|
128120
128133
|
}
|
|
128121
128134
|
(0, import_react.useEffect)(() => {
|
|
@@ -152917,7 +152930,7 @@ function InsightsView() {
|
|
|
152917
152930
|
setProjects(obs.projects);
|
|
152918
152931
|
setNow(Date.now());
|
|
152919
152932
|
} catch (e) {
|
|
152920
|
-
setError(
|
|
152933
|
+
setError(errMessage(e));
|
|
152921
152934
|
}
|
|
152922
152935
|
}
|
|
152923
152936
|
(0, import_react.useEffect)(() => {
|
|
@@ -152937,7 +152950,7 @@ function InsightsView() {
|
|
|
152937
152950
|
setVersion(entry.version);
|
|
152938
152951
|
} catch (e) {
|
|
152939
152952
|
setConfig(previous);
|
|
152940
|
-
setError(
|
|
152953
|
+
setError(errMessage(e));
|
|
152941
152954
|
} finally {
|
|
152942
152955
|
setSaving(false);
|
|
152943
152956
|
}
|
|
@@ -154067,7 +154080,7 @@ function AgentDiagnostics({ agent, orchestrators }) {
|
|
|
154067
154080
|
});
|
|
154068
154081
|
const policyName = typeof agent.meta?.policyName === "string" ? agent.meta.policyName : null;
|
|
154069
154082
|
const profileName = typeof agent.meta?.profile === "string" ? agent.meta.profile : null;
|
|
154070
|
-
const agentProfile = isRecord
|
|
154083
|
+
const agentProfile = isRecord(agent.meta?.agentProfile) ? agent.meta.agentProfile : null;
|
|
154071
154084
|
const profileProjection = isProfileProjectionReport(agentProfile?.projection) ? agentProfile.projection : null;
|
|
154072
154085
|
const managedOrch = orchestrators.find((o) => o.managedAgents.some((m) => m.agentId === agent.id));
|
|
154073
154086
|
const managedAgent = managedOrch?.managedAgents.find((m) => m.agentId === agent.id);
|
|
@@ -154558,11 +154571,8 @@ function CollapsibleSection({ title, expanded, onToggle, children }) {
|
|
|
154558
154571
|
})]
|
|
154559
154572
|
});
|
|
154560
154573
|
}
|
|
154561
|
-
function isRecord$1(value) {
|
|
154562
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
154563
|
-
}
|
|
154564
154574
|
function isProfileProjectionReport(value) {
|
|
154565
|
-
return isRecord
|
|
154575
|
+
return isRecord(value) && typeof value.profileName === "string" && typeof value.provider === "string" && typeof value.base === "string" && Array.isArray(value.entries) && Array.isArray(value.warnings) && Array.isArray(value.unsupported);
|
|
154566
154576
|
}
|
|
154567
154577
|
function KV({ label, value, className = "", mono = false }) {
|
|
154568
154578
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
@@ -155753,9 +155763,6 @@ function orchestratorForAgentTerminal(agent, session, orchestrators) {
|
|
|
155753
155763
|
if (!agent) return null;
|
|
155754
155764
|
return orchestrators.find((orchestrator) => orchestrator.managedAgents.some((managed) => managed.agentId === agent.id || managed.tmuxSession === session)) ?? null;
|
|
155755
155765
|
}
|
|
155756
|
-
function isRecord(value) {
|
|
155757
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
155758
|
-
}
|
|
155759
155766
|
//#endregion
|
|
155760
155767
|
//#region src/components/drawers/channel-detail-drawer.tsx
|
|
155761
155768
|
function ChannelDetailDrawer() {
|
|
@@ -156467,7 +156474,7 @@ function DirectoryBrowserDialog({ open, onOpenChange, onSelect, orchestratorId,
|
|
|
156467
156474
|
const query = path ? `?path=${encodeURIComponent(path)}` : "";
|
|
156468
156475
|
setListing(await apiCall("GET", `/orchestrators/${encodeURIComponent(orchestratorId)}/directories${query}`));
|
|
156469
156476
|
} catch (e) {
|
|
156470
|
-
setError(
|
|
156477
|
+
setError(errMessage(e));
|
|
156471
156478
|
} finally {
|
|
156472
156479
|
setLoading(false);
|
|
156473
156480
|
}
|
|
@@ -156491,7 +156498,7 @@ function DirectoryBrowserDialog({ open, onOpenChange, onSelect, orchestratorId,
|
|
|
156491
156498
|
setCreating(false);
|
|
156492
156499
|
setNewDirName("");
|
|
156493
156500
|
} catch (e) {
|
|
156494
|
-
setCreateError(
|
|
156501
|
+
setCreateError(errMessage(e));
|
|
156495
156502
|
}
|
|
156496
156503
|
}
|
|
156497
156504
|
function handleSelect() {
|
package/public/sw.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const CACHE_NAME = "agent-relay-dashboard-
|
|
1
|
+
const CACHE_NAME = "agent-relay-dashboard-v2";
|
|
2
2
|
const scopeUrl = new URL(self.registration.scope);
|
|
3
3
|
const scopePath = scopeUrl.pathname.endsWith("/") ? scopeUrl.pathname : `${scopeUrl.pathname}/`;
|
|
4
4
|
const appUrl = (path) => new URL(path, self.registration.scope).toString();
|
|
@@ -11,10 +11,30 @@ const APP_SHELL = [
|
|
|
11
11
|
appUrl("icons/agent-relay-512.png"),
|
|
12
12
|
];
|
|
13
13
|
|
|
14
|
+
// The server may send the shell brotli/gzip-encoded. fetch() exposes the body
|
|
15
|
+
// already decoded but leaves Content-Encoding / Content-Length headers on the
|
|
16
|
+
// response — caching that verbatim risks a double-decode or
|
|
17
|
+
// ERR_CONTENT_LENGTH_MISMATCH on replay. Re-wrap with those headers stripped so
|
|
18
|
+
// the cached copy is clean identity bytes.
|
|
19
|
+
async function cacheNormalized(cache, key, response) {
|
|
20
|
+
const body = await response.clone().arrayBuffer();
|
|
21
|
+
const headers = new Headers(response.headers);
|
|
22
|
+
headers.delete("Content-Encoding");
|
|
23
|
+
headers.delete("Content-Length");
|
|
24
|
+
await cache.put(key, new Response(body, {
|
|
25
|
+
status: response.status,
|
|
26
|
+
statusText: response.statusText,
|
|
27
|
+
headers,
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
|
|
14
31
|
self.addEventListener("install", (event) => {
|
|
15
32
|
event.waitUntil(
|
|
16
33
|
caches.open(CACHE_NAME)
|
|
17
|
-
.then((cache) =>
|
|
34
|
+
.then((cache) => Promise.all(APP_SHELL.map(async (href) => {
|
|
35
|
+
const response = await fetch(href, { cache: "reload" });
|
|
36
|
+
if (response.ok) await cacheNormalized(cache, href, response);
|
|
37
|
+
})))
|
|
18
38
|
.then(() => self.skipWaiting()),
|
|
19
39
|
);
|
|
20
40
|
});
|
|
@@ -45,20 +65,35 @@ self.addEventListener("fetch", (event) => {
|
|
|
45
65
|
return;
|
|
46
66
|
}
|
|
47
67
|
|
|
68
|
+
// Stale-while-revalidate for the app shell and SPA navigations: serve the
|
|
69
|
+
// cached bundle instantly (the ~10 MB unminified shell never blocks the
|
|
70
|
+
// network on a slow/high-latency link) and refresh it in the background.
|
|
71
|
+
// SPA route navigations all resolve to index.html, so key them there to avoid
|
|
72
|
+
// fragmenting the cache by query string / deep-link path.
|
|
73
|
+
const isNavigation = request.mode === "navigate";
|
|
74
|
+
if (isNavigation || APP_SHELL.includes(url.href)) {
|
|
75
|
+
event.respondWith(staleWhileRevalidate(request, isNavigation ? appUrl("index.html") : request));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Other same-scope assets: network-first, fall back to cache when offline.
|
|
48
80
|
event.respondWith(
|
|
49
|
-
fetch(request)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
caches.open(CACHE_NAME).then((cache) => cache.put(request, copy));
|
|
54
|
-
}
|
|
55
|
-
return response;
|
|
56
|
-
})
|
|
57
|
-
.catch(async () => {
|
|
58
|
-
const cached = await caches.match(request);
|
|
59
|
-
if (cached) return cached;
|
|
60
|
-
if (request.mode === "navigate") return caches.match(appUrl("index.html"));
|
|
61
|
-
throw new Error("offline");
|
|
62
|
-
}),
|
|
81
|
+
fetch(request).catch(() => caches.match(request).then((c) => {
|
|
82
|
+
if (c) return c;
|
|
83
|
+
throw new Error("offline");
|
|
84
|
+
})),
|
|
63
85
|
);
|
|
64
86
|
});
|
|
87
|
+
|
|
88
|
+
async function staleWhileRevalidate(request, cacheKey) {
|
|
89
|
+
const cache = await caches.open(CACHE_NAME);
|
|
90
|
+
const cached = await cache.match(cacheKey);
|
|
91
|
+
const network = fetch(request)
|
|
92
|
+
.then(async (response) => {
|
|
93
|
+
if (response.ok) await cacheNormalized(cache, cacheKey, response);
|
|
94
|
+
return response;
|
|
95
|
+
})
|
|
96
|
+
.catch(() => cached);
|
|
97
|
+
// Return cache immediately when present; otherwise wait for the network.
|
|
98
|
+
return cached || network;
|
|
99
|
+
}
|
package/runner/src/adapter.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AgentProfile, Message } from "agent-relay-sdk";
|
|
2
|
+
import { isRecord } from "agent-relay-sdk";
|
|
2
3
|
|
|
3
4
|
export type SemanticStatus = "idle" | "busy" | "offline" | "error";
|
|
4
5
|
type ProviderWorkKind = "provider-turn" | "subagent";
|
|
@@ -160,10 +161,6 @@ export const RELAY_CONTEXT = `[agent-relay] You are connected to Agent Relay, a
|
|
|
160
161
|
|
|
161
162
|
const PROVIDER_MESSAGE_BODY_PREVIEW_CHARS = 4000;
|
|
162
163
|
|
|
163
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
164
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
165
|
-
}
|
|
166
|
-
|
|
167
164
|
function attachmentRefs(message: Message): Record<string, unknown>[] {
|
|
168
165
|
const payloadRefs = message.payload?.attachments;
|
|
169
166
|
const topLevelRefs = (message as Message & { attachments?: unknown }).attachments;
|
package/runner/src/config.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { homedir, hostname } from "node:os";
|
|
3
3
|
import { join, resolve } from "node:path";
|
|
4
|
+
import { stringValue } from "agent-relay-sdk";
|
|
5
|
+
import { sanitizeFsName } from "agent-relay-sdk/fs-name";
|
|
4
6
|
import type { ProviderConfig } from "./adapter";
|
|
5
7
|
|
|
6
8
|
interface GlobalRunnerConfig {
|
|
@@ -97,7 +99,7 @@ export function providerConfigPublic(config: LoadedProviderConfig): Record<strin
|
|
|
97
99
|
|
|
98
100
|
export function runnerId(provider: string, cwd: string, label?: string): string {
|
|
99
101
|
const project = cwd.split("/").filter(Boolean).at(-1) || "workspace";
|
|
100
|
-
const cleanLabel = (label || project
|
|
102
|
+
const cleanLabel = sanitizeFsName(label || project, { replacement: "-", lowercase: true });
|
|
101
103
|
return `${hostname()}-${provider}-${cleanLabel}-${crypto.randomUUID().slice(0, 8)}`;
|
|
102
104
|
}
|
|
103
105
|
|
|
@@ -115,10 +117,6 @@ function readJson(path: string): Record<string, unknown> {
|
|
|
115
117
|
}
|
|
116
118
|
}
|
|
117
119
|
|
|
118
|
-
function stringValue(value: unknown): string | undefined {
|
|
119
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
120
|
function positiveInteger(value: unknown): number | undefined {
|
|
123
121
|
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
|
|
124
122
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { hostname } from "node:os";
|
|
3
|
+
import { RELAY_TOKEN_HEADER } from "agent-relay-sdk";
|
|
3
4
|
|
|
4
5
|
type Orchestrator = {
|
|
5
6
|
id: string;
|
|
@@ -44,7 +45,7 @@ relayUrl = relayUrl.replace(/\/+$/, "");
|
|
|
44
45
|
|
|
45
46
|
function headers(): Record<string, string> {
|
|
46
47
|
const h: Record<string, string> = { "Content-Type": "application/json" };
|
|
47
|
-
if (process.env.AGENT_RELAY_TOKEN) h[
|
|
48
|
+
if (process.env.AGENT_RELAY_TOKEN) h[RELAY_TOKEN_HEADER] = process.env.AGENT_RELAY_TOKEN;
|
|
48
49
|
return h;
|
|
49
50
|
}
|
|
50
51
|
|