agent-relay-server 0.16.0 → 0.18.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 +201 -1
- package/package.json +2 -2
- package/public/index.html +100 -25
- package/public/sw.js +51 -16
- package/runner/src/adapter.ts +1 -4
- package/runner/src/config.ts +1 -4
- package/scripts/orchestrator-spawn-smoke.ts +2 -1
- package/src/automations.ts +8 -31
- package/src/bus.ts +2 -17
- package/src/cli.ts +179 -3
- package/src/command-events.ts +26 -0
- package/src/config-store.ts +64 -22
- package/src/connectors.ts +1 -4
- package/src/contracts.ts +2 -8
- package/src/db.ts +36 -18
- package/src/index.ts +99 -4
- package/src/lifecycle-manager.ts +11 -24
- package/src/maintenance.ts +26 -20
- package/src/managed-policy.ts +8 -26
- package/src/mcp.ts +19 -43
- package/src/memory-broker-smoke.ts +3 -1
- package/src/memory-command-broker.ts +1 -4
- package/src/memory-http-broker.ts +1 -4
- package/src/memory-service.ts +1 -4
- package/src/memory-sqlite-broker.ts +1 -8
- package/src/provider-catalog-store.ts +3 -11
- package/src/recipe-loader.ts +1 -4
- package/src/recipe-validator.ts +1 -4
- package/src/routes.ts +290 -139
- package/src/security.ts +3 -7
- package/src/spawn-command.ts +150 -0
- package/src/sse.ts +1 -4
- package/src/steward.ts +16 -21
- package/src/upgrade.ts +3 -2
- package/src/utils.ts +38 -0
- package/src/validation.ts +28 -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",
|
|
@@ -5957,6 +6057,100 @@
|
|
|
5957
6057
|
]
|
|
5958
6058
|
}
|
|
5959
6059
|
},
|
|
6060
|
+
"/api/workspace-config": {
|
|
6061
|
+
"get": {
|
|
6062
|
+
"operationId": "getWorkspaceConfigRoute",
|
|
6063
|
+
"summary": "Get global workspace (isolated worktree) config",
|
|
6064
|
+
"tags": [
|
|
6065
|
+
"Other"
|
|
6066
|
+
],
|
|
6067
|
+
"description": "Returns the global workspace provisioning config `{ symlinkPaths: string[] }` — untracked files/filename patterns symlinked from the main checkout into each isolated worktree (the dashboard \"Workspace\" spawn option). Defaults seed `AGENTS.md` and `.claude-rig`; merged over defaults until set.",
|
|
6068
|
+
"responses": {
|
|
6069
|
+
"200": {
|
|
6070
|
+
"description": "Success",
|
|
6071
|
+
"content": {
|
|
6072
|
+
"application/json": {}
|
|
6073
|
+
}
|
|
6074
|
+
},
|
|
6075
|
+
"400": {
|
|
6076
|
+
"description": "Bad request",
|
|
6077
|
+
"content": {
|
|
6078
|
+
"application/json": {
|
|
6079
|
+
"schema": {
|
|
6080
|
+
"$ref": "#/components/schemas/Error"
|
|
6081
|
+
}
|
|
6082
|
+
}
|
|
6083
|
+
}
|
|
6084
|
+
}
|
|
6085
|
+
},
|
|
6086
|
+
"security": [
|
|
6087
|
+
{
|
|
6088
|
+
"bearerAuth": []
|
|
6089
|
+
},
|
|
6090
|
+
{
|
|
6091
|
+
"tokenHeader": []
|
|
6092
|
+
},
|
|
6093
|
+
{
|
|
6094
|
+
"tokenQuery": []
|
|
6095
|
+
}
|
|
6096
|
+
]
|
|
6097
|
+
},
|
|
6098
|
+
"put": {
|
|
6099
|
+
"operationId": "putWorkspaceConfigRoute",
|
|
6100
|
+
"summary": "Set global workspace (isolated worktree) config",
|
|
6101
|
+
"tags": [
|
|
6102
|
+
"Other"
|
|
6103
|
+
],
|
|
6104
|
+
"description": "Updates the global workspace config. Body: `{ value: { symlinkPaths: [...] }, updatedBy? }`. Each entry is a relative path or glob pattern; plain names match files and directories, entries with `*?[]{}` are expanded against main. A path is only linked if it exists in main — missing entries are ignored at spawn time. Rejects absolute paths and `..` traversal (400). Versioned with full config history.",
|
|
6105
|
+
"requestBody": {
|
|
6106
|
+
"required": true,
|
|
6107
|
+
"content": {
|
|
6108
|
+
"application/json": {
|
|
6109
|
+
"schema": {
|
|
6110
|
+
"type": "object",
|
|
6111
|
+
"properties": {
|
|
6112
|
+
"value": {
|
|
6113
|
+
"type": "string"
|
|
6114
|
+
},
|
|
6115
|
+
"updatedBy": {
|
|
6116
|
+
"type": "string"
|
|
6117
|
+
}
|
|
6118
|
+
}
|
|
6119
|
+
}
|
|
6120
|
+
}
|
|
6121
|
+
}
|
|
6122
|
+
},
|
|
6123
|
+
"responses": {
|
|
6124
|
+
"200": {
|
|
6125
|
+
"description": "Success",
|
|
6126
|
+
"content": {
|
|
6127
|
+
"application/json": {}
|
|
6128
|
+
}
|
|
6129
|
+
},
|
|
6130
|
+
"400": {
|
|
6131
|
+
"description": "Bad request",
|
|
6132
|
+
"content": {
|
|
6133
|
+
"application/json": {
|
|
6134
|
+
"schema": {
|
|
6135
|
+
"$ref": "#/components/schemas/Error"
|
|
6136
|
+
}
|
|
6137
|
+
}
|
|
6138
|
+
}
|
|
6139
|
+
}
|
|
6140
|
+
},
|
|
6141
|
+
"security": [
|
|
6142
|
+
{
|
|
6143
|
+
"bearerAuth": []
|
|
6144
|
+
},
|
|
6145
|
+
{
|
|
6146
|
+
"tokenHeader": []
|
|
6147
|
+
},
|
|
6148
|
+
{
|
|
6149
|
+
"tokenQuery": []
|
|
6150
|
+
}
|
|
6151
|
+
]
|
|
6152
|
+
}
|
|
6153
|
+
},
|
|
5960
6154
|
"/api/insights/config": {
|
|
5961
6155
|
"get": {
|
|
5962
6156
|
"operationId": "getInsightsConfigRoute",
|
|
@@ -6143,6 +6337,9 @@
|
|
|
6143
6337
|
},
|
|
6144
6338
|
"source": {
|
|
6145
6339
|
"type": "string"
|
|
6340
|
+
},
|
|
6341
|
+
"occurredAt": {
|
|
6342
|
+
"type": "string"
|
|
6146
6343
|
}
|
|
6147
6344
|
}
|
|
6148
6345
|
}
|
|
@@ -7217,6 +7414,9 @@
|
|
|
7217
7414
|
"maxAgeSeconds": {
|
|
7218
7415
|
"type": "string"
|
|
7219
7416
|
},
|
|
7417
|
+
"occurredAt": {
|
|
7418
|
+
"type": "string"
|
|
7419
|
+
},
|
|
7220
7420
|
"channel": {
|
|
7221
7421
|
"type": "string"
|
|
7222
7422
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-relay-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.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.9"
|
|
37
37
|
},
|
|
38
38
|
"scripts": {
|
|
39
39
|
"prepack": "bun run build:dashboard:bundle >&2",
|
package/public/index.html
CHANGED
|
@@ -10816,6 +10816,21 @@ 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
|
+
//#endregion
|
|
10819
10834
|
//#region src/lib/display.ts
|
|
10820
10835
|
function toTimestamp(value) {
|
|
10821
10836
|
const ts = typeof value === "number" ? value : new Date(value || 0).getTime();
|
|
@@ -10901,7 +10916,7 @@ function isAgentStale(now, agent) {
|
|
|
10901
10916
|
if (!agent?.lastSeen || agent.status === "offline") return false;
|
|
10902
10917
|
if (agent.id === "user" || agent.id === "system") return false;
|
|
10903
10918
|
const transport = agent.meta?.transport;
|
|
10904
|
-
if (isRecord
|
|
10919
|
+
if (isRecord(transport) && transport.connected === true) return false;
|
|
10905
10920
|
const lastSeenMs = new Date(agent.lastSeen).getTime();
|
|
10906
10921
|
if (!Number.isFinite(lastSeenMs)) return false;
|
|
10907
10922
|
return now - lastSeenMs > 6e4;
|
|
@@ -11060,7 +11075,7 @@ function messageMatchesChannel(message, channel) {
|
|
|
11060
11075
|
}
|
|
11061
11076
|
function providerRuntimeState(agent) {
|
|
11062
11077
|
const raw = agent?.meta?.providerState;
|
|
11063
|
-
if (!isRecord
|
|
11078
|
+
if (!isRecord(raw) || typeof raw.state !== "string") return null;
|
|
11064
11079
|
return {
|
|
11065
11080
|
state: raw.state,
|
|
11066
11081
|
reason: typeof raw.reason === "string" ? raw.reason : void 0,
|
|
@@ -11073,16 +11088,16 @@ function providerRuntimeState(agent) {
|
|
|
11073
11088
|
};
|
|
11074
11089
|
}
|
|
11075
11090
|
function providerPendingApproval(value) {
|
|
11076
|
-
if (!isRecord
|
|
11077
|
-
const choices = Array.isArray(value.choices) ? value.choices.filter(isRecord
|
|
11091
|
+
if (!isRecord(value) || typeof value.id !== "string") return void 0;
|
|
11092
|
+
const choices = Array.isArray(value.choices) ? value.choices.filter(isRecord).map((choice) => ({
|
|
11078
11093
|
id: choice.id,
|
|
11079
11094
|
label: typeof choice.label === "string" ? choice.label : String(choice.id || "")
|
|
11080
11095
|
})).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
|
|
11096
|
+
const questions = Array.isArray(value.questions) ? value.questions.filter(isRecord).map((q) => ({
|
|
11082
11097
|
question: typeof q.question === "string" ? q.question : "",
|
|
11083
11098
|
header: typeof q.header === "string" ? q.header : void 0,
|
|
11084
11099
|
multiSelect: q.multiSelect === true,
|
|
11085
|
-
options: Array.isArray(q.options) ? q.options.filter(isRecord
|
|
11100
|
+
options: Array.isArray(q.options) ? q.options.filter(isRecord).map((o) => ({
|
|
11086
11101
|
label: typeof o.label === "string" ? o.label : String(o.label ?? ""),
|
|
11087
11102
|
description: typeof o.description === "string" ? o.description : void 0
|
|
11088
11103
|
})).filter((o) => Boolean(o.label)) : []
|
|
@@ -11110,7 +11125,7 @@ function activeSubagents(agent) {
|
|
|
11110
11125
|
label: `Subagent ${index + 1}`
|
|
11111
11126
|
})) : [];
|
|
11112
11127
|
}
|
|
11113
|
-
return raw.filter(isRecord
|
|
11128
|
+
return raw.filter(isRecord).map((item, index) => {
|
|
11114
11129
|
const id = typeof item.id === "string" && item.id ? item.id : `subagent-${index + 1}`;
|
|
11115
11130
|
const role = typeof item.role === "string" && item.role ? item.role : void 0;
|
|
11116
11131
|
return {
|
|
@@ -11256,9 +11271,6 @@ function presenceBadges(agent, attention, pair) {
|
|
|
11256
11271
|
});
|
|
11257
11272
|
return badges;
|
|
11258
11273
|
}
|
|
11259
|
-
function isRecord$2(value) {
|
|
11260
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
11261
|
-
}
|
|
11262
11274
|
function emptyAttention() {
|
|
11263
11275
|
return {
|
|
11264
11276
|
unread: 0,
|
|
@@ -11525,7 +11537,7 @@ function downloadText(filename, text, type) {
|
|
|
11525
11537
|
URL.revokeObjectURL(url);
|
|
11526
11538
|
}
|
|
11527
11539
|
//#endregion
|
|
11528
|
-
//#region ../sdk/
|
|
11540
|
+
//#region ../sdk/src/provider-catalog.ts
|
|
11529
11541
|
var CLAUDE_LOW_TO_MAX = [
|
|
11530
11542
|
"low",
|
|
11531
11543
|
"medium",
|
|
@@ -108614,9 +108626,6 @@ function workspaceMode(agent) {
|
|
|
108614
108626
|
function recordValue(value) {
|
|
108615
108627
|
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
108616
108628
|
}
|
|
108617
|
-
function stringValue(value) {
|
|
108618
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
108619
|
-
}
|
|
108620
108629
|
function runtimeBadges(agent) {
|
|
108621
108630
|
const caps = agent.providerCapabilities;
|
|
108622
108631
|
const context = agent.context;
|
|
@@ -125641,6 +125650,9 @@ function chatTimestamp(iso) {
|
|
|
125641
125650
|
minute: "2-digit"
|
|
125642
125651
|
});
|
|
125643
125652
|
}
|
|
125653
|
+
function messageEventTime(msg) {
|
|
125654
|
+
return new Date(msg.occurredAt ?? msg.createdAt).getTime();
|
|
125655
|
+
}
|
|
125644
125656
|
function dateSeparatorLabel(dateStr) {
|
|
125645
125657
|
const today = /* @__PURE__ */ new Date();
|
|
125646
125658
|
const [y, m, d] = dateStr.split("-").map(Number);
|
|
@@ -126444,7 +126456,7 @@ var MessageBubble = (0, import_react.memo)(function MessageBubble({ msg, peer, o
|
|
|
126444
126456
|
const pointerStartRef = (0, import_react.useRef)(null);
|
|
126445
126457
|
const suppressBubbleClickRef = (0, import_react.useRef)(false);
|
|
126446
126458
|
const body = messageBody(msg);
|
|
126447
|
-
const time = chatTimestamp(msg.createdAt);
|
|
126459
|
+
const time = chatTimestamp(msg.occurredAt ?? msg.createdAt);
|
|
126448
126460
|
const attachments = messageAttachments(msg);
|
|
126449
126461
|
const reactions = groupedReactions(msg);
|
|
126450
126462
|
const receipt = isOutbound ? outboundReceipt(msg, peer) : null;
|
|
@@ -126580,7 +126592,7 @@ function sessionActivityStep(msg) {
|
|
|
126580
126592
|
kind: s.type,
|
|
126581
126593
|
label: typeof s.label === "string" ? s.label : void 0,
|
|
126582
126594
|
text: msg.body,
|
|
126583
|
-
ts:
|
|
126595
|
+
ts: messageEventTime(msg),
|
|
126584
126596
|
turnId: typeof s.turnId === "string" ? s.turnId : void 0
|
|
126585
126597
|
};
|
|
126586
126598
|
}
|
|
@@ -126674,7 +126686,7 @@ function buildTimeline(messages, statusEvents, createdAt, importedHistory = [])
|
|
|
126674
126686
|
continue;
|
|
126675
126687
|
}
|
|
126676
126688
|
raw.push({
|
|
126677
|
-
ts:
|
|
126689
|
+
ts: messageEventTime(msg),
|
|
126678
126690
|
entry: {
|
|
126679
126691
|
type: "message",
|
|
126680
126692
|
msg
|
|
@@ -153682,11 +153694,80 @@ function SettingsView() {
|
|
|
153682
153694
|
]
|
|
153683
153695
|
})]
|
|
153684
153696
|
}),
|
|
153697
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(WorkspaceSettings, {}),
|
|
153685
153698
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(StewardSettings, {}),
|
|
153686
153699
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(VoiceSettings, {})
|
|
153687
153700
|
]
|
|
153688
153701
|
});
|
|
153689
153702
|
}
|
|
153703
|
+
var DEFAULT_WORKSPACE = { symlinkPaths: [] };
|
|
153704
|
+
function WorkspaceSettings() {
|
|
153705
|
+
const [config, setConfig] = (0, import_react.useState)(DEFAULT_WORKSPACE);
|
|
153706
|
+
const [status, setStatus] = (0, import_react.useState)("");
|
|
153707
|
+
(0, import_react.useEffect)(() => {
|
|
153708
|
+
api("GET", "/workspace-config").then((entry) => setConfig({
|
|
153709
|
+
...DEFAULT_WORKSPACE,
|
|
153710
|
+
...entry.value
|
|
153711
|
+
})).catch((e) => setStatus(e.message));
|
|
153712
|
+
}, []);
|
|
153713
|
+
async function save() {
|
|
153714
|
+
try {
|
|
153715
|
+
const saved = await api("PUT", "/workspace-config", config);
|
|
153716
|
+
setConfig({
|
|
153717
|
+
...DEFAULT_WORKSPACE,
|
|
153718
|
+
...saved.value
|
|
153719
|
+
});
|
|
153720
|
+
setStatus("Workspace config saved");
|
|
153721
|
+
} catch (e) {
|
|
153722
|
+
setStatus(e.message);
|
|
153723
|
+
}
|
|
153724
|
+
}
|
|
153725
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", {
|
|
153726
|
+
className: "space-y-3 rounded-lg border p-4",
|
|
153727
|
+
children: [
|
|
153728
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
153729
|
+
className: "flex items-center gap-2",
|
|
153730
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(FolderSymlink, { className: "w-4 h-4" }), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", {
|
|
153731
|
+
className: "text-sm font-semibold",
|
|
153732
|
+
children: "Isolated worktree symlinks"
|
|
153733
|
+
}), /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", {
|
|
153734
|
+
className: "text-xs text-muted-foreground",
|
|
153735
|
+
children: [
|
|
153736
|
+
"Untracked files or filename patterns symlinked from main into each isolated worktree (the \"Workspace\" spawn option), one per line. Plain names like ",
|
|
153737
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: "AGENTS.md" }),
|
|
153738
|
+
" or ",
|
|
153739
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: ".claude-rig" }),
|
|
153740
|
+
" match files and directories; entries with ",
|
|
153741
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("code", { children: ["*?[]", "{}"] }),
|
|
153742
|
+
" are expanded as globs. A path is only linked if it exists in main."
|
|
153743
|
+
]
|
|
153744
|
+
})] })]
|
|
153745
|
+
}),
|
|
153746
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Field, {
|
|
153747
|
+
label: "Paths / patterns",
|
|
153748
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Textarea, {
|
|
153749
|
+
rows: 5,
|
|
153750
|
+
placeholder: "AGENTS.md\n.claude-rig",
|
|
153751
|
+
value: config.symlinkPaths.join("\n"),
|
|
153752
|
+
onChange: (e) => setConfig({
|
|
153753
|
+
...config,
|
|
153754
|
+
symlinkPaths: lines(e.target.value)
|
|
153755
|
+
})
|
|
153756
|
+
})
|
|
153757
|
+
}),
|
|
153758
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
153759
|
+
className: "flex items-center gap-2 pt-1",
|
|
153760
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Button, {
|
|
153761
|
+
onClick: save,
|
|
153762
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(Save, { className: "w-4 h-4" }), "Save"]
|
|
153763
|
+
}), status && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", {
|
|
153764
|
+
className: "text-sm text-muted-foreground",
|
|
153765
|
+
children: status
|
|
153766
|
+
})]
|
|
153767
|
+
})
|
|
153768
|
+
]
|
|
153769
|
+
});
|
|
153770
|
+
}
|
|
153690
153771
|
function VoiceSettings() {
|
|
153691
153772
|
const voiceInputMode = useRelayStore((s) => s.voiceInputMode);
|
|
153692
153773
|
const setVoiceInputMode = useRelayStore((s) => s.setVoiceInputMode);
|
|
@@ -153995,7 +154076,7 @@ function AgentDiagnostics({ agent, orchestrators }) {
|
|
|
153995
154076
|
});
|
|
153996
154077
|
const policyName = typeof agent.meta?.policyName === "string" ? agent.meta.policyName : null;
|
|
153997
154078
|
const profileName = typeof agent.meta?.profile === "string" ? agent.meta.profile : null;
|
|
153998
|
-
const agentProfile = isRecord
|
|
154079
|
+
const agentProfile = isRecord(agent.meta?.agentProfile) ? agent.meta.agentProfile : null;
|
|
153999
154080
|
const profileProjection = isProfileProjectionReport(agentProfile?.projection) ? agentProfile.projection : null;
|
|
154000
154081
|
const managedOrch = orchestrators.find((o) => o.managedAgents.some((m) => m.agentId === agent.id));
|
|
154001
154082
|
const managedAgent = managedOrch?.managedAgents.find((m) => m.agentId === agent.id);
|
|
@@ -154486,11 +154567,8 @@ function CollapsibleSection({ title, expanded, onToggle, children }) {
|
|
|
154486
154567
|
})]
|
|
154487
154568
|
});
|
|
154488
154569
|
}
|
|
154489
|
-
function isRecord$1(value) {
|
|
154490
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
154491
|
-
}
|
|
154492
154570
|
function isProfileProjectionReport(value) {
|
|
154493
|
-
return isRecord
|
|
154571
|
+
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);
|
|
154494
154572
|
}
|
|
154495
154573
|
function KV({ label, value, className = "", mono = false }) {
|
|
154496
154574
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
@@ -155681,9 +155759,6 @@ function orchestratorForAgentTerminal(agent, session, orchestrators) {
|
|
|
155681
155759
|
if (!agent) return null;
|
|
155682
155760
|
return orchestrators.find((orchestrator) => orchestrator.managedAgents.some((managed) => managed.agentId === agent.id || managed.tmuxSession === session)) ?? null;
|
|
155683
155761
|
}
|
|
155684
|
-
function isRecord(value) {
|
|
155685
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
155686
|
-
}
|
|
155687
155762
|
//#endregion
|
|
155688
155763
|
//#region src/components/drawers/channel-detail-drawer.tsx
|
|
155689
155764
|
function ChannelDetailDrawer() {
|
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,7 @@
|
|
|
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";
|
|
4
5
|
import type { ProviderConfig } from "./adapter";
|
|
5
6
|
|
|
6
7
|
interface GlobalRunnerConfig {
|
|
@@ -115,10 +116,6 @@ function readJson(path: string): Record<string, unknown> {
|
|
|
115
116
|
}
|
|
116
117
|
}
|
|
117
118
|
|
|
118
|
-
function stringValue(value: unknown): string | undefined {
|
|
119
|
-
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
119
|
function positiveInteger(value: unknown): number | undefined {
|
|
123
120
|
return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : undefined;
|
|
124
121
|
}
|
|
@@ -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
|
|