@kelloggt/dsh-program-agents 0.0.0-stage → 0.1.1
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/LICENSE +21 -0
- package/README.md +90 -2
- package/cordis.patch.yml +5 -0
- package/dist/app/agents.d.ts +50 -0
- package/dist/app/agents.js +70 -0
- package/dist/app/agents.js.map +1 -0
- package/dist/app/conversations.d.ts +8 -0
- package/dist/app/conversations.js +26 -0
- package/dist/app/conversations.js.map +1 -0
- package/dist/app/endpoints.d.ts +36 -0
- package/dist/app/endpoints.js +162 -0
- package/dist/app/endpoints.js.map +1 -0
- package/dist/app/inbox.d.ts +61 -0
- package/dist/app/inbox.js +253 -0
- package/dist/app/inbox.js.map +1 -0
- package/dist/app/index.d.ts +70 -0
- package/dist/app/index.js +147 -0
- package/dist/app/index.js.map +1 -0
- package/dist/app/mailbox.d.ts +63 -0
- package/dist/app/mailbox.js +173 -0
- package/dist/app/mailbox.js.map +1 -0
- package/dist/app/models.d.ts +32 -0
- package/dist/app/models.js +13 -0
- package/dist/app/models.js.map +1 -0
- package/dist/app/server.d.ts +36 -0
- package/dist/app/server.js +233 -0
- package/dist/app/server.js.map +1 -0
- package/dist/app/stream-server.d.ts +9 -0
- package/dist/app/stream-server.js +77 -0
- package/dist/app/stream-server.js.map +1 -0
- package/dist/app/stream.d.ts +4 -0
- package/dist/app/stream.js +93 -0
- package/dist/app/stream.js.map +1 -0
- package/dist/app/tools.d.ts +16 -0
- package/dist/app/tools.js +24 -0
- package/dist/app/tools.js.map +1 -0
- package/dist/client/controller.d.ts +21 -0
- package/dist/client/controller.js +140 -0
- package/dist/client/controller.js.map +1 -0
- package/dist/client/index.d.ts +22 -0
- package/dist/client/index.js +14 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/model.d.ts +66 -0
- package/dist/client/model.js +147 -0
- package/dist/client/model.js.map +1 -0
- package/dist/client/models.d.ts +11 -0
- package/dist/client/models.js +27 -0
- package/dist/client/models.js.map +1 -0
- package/dist/client/transport.d.ts +64 -0
- package/dist/client/transport.js +141 -0
- package/dist/client/transport.js.map +1 -0
- package/dist/client/view.d.ts +19 -0
- package/dist/client/view.js +69 -0
- package/dist/client/view.js.map +1 -0
- package/dist/client.js +673 -0
- package/dist/conversations.d.ts +92 -0
- package/dist/conversations.js +6 -0
- package/dist/conversations.js.map +1 -0
- package/dist/endpoints.d.ts +48 -0
- package/dist/endpoints.js +43 -0
- package/dist/endpoints.js.map +1 -0
- package/dist/examples/dashboard.js +42 -0
- package/dist/examples/dynamic-server.js +27 -0
- package/dist/examples/inbox-worker.js +22 -0
- package/dist/examples/model-slots.js +19 -0
- package/dist/examples/standalone-supervisor.js +20 -0
- package/dist/host/adapter.d.ts +195 -0
- package/dist/host/adapter.js +630 -0
- package/dist/host/adapter.js.map +1 -0
- package/dist/host/agents.d.ts +35 -0
- package/dist/host/agents.js +222 -0
- package/dist/host/agents.js.map +1 -0
- package/dist/host/audit.d.ts +14 -0
- package/dist/host/audit.js +39 -0
- package/dist/host/audit.js.map +1 -0
- package/dist/host/catalog.d.ts +53 -0
- package/dist/host/catalog.js +116 -0
- package/dist/host/catalog.js.map +1 -0
- package/dist/host/contracts.d.ts +38 -0
- package/dist/host/contracts.js +2 -0
- package/dist/host/contracts.js.map +1 -0
- package/dist/host/conversations.d.ts +41 -0
- package/dist/host/conversations.js +324 -0
- package/dist/host/conversations.js.map +1 -0
- package/dist/host/events.d.ts +12 -0
- package/dist/host/events.js +106 -0
- package/dist/host/events.js.map +1 -0
- package/dist/host/http.d.ts +14 -0
- package/dist/host/http.js +30 -0
- package/dist/host/http.js.map +1 -0
- package/dist/host/index.d.ts +23 -0
- package/dist/host/index.js +86 -0
- package/dist/host/index.js.map +1 -0
- package/dist/host/installed-v10.js +27181 -0
- package/dist/host/installed-v11.js +27181 -0
- package/dist/host/installed-v12.js +27181 -0
- package/dist/host/installed-v13.js +27181 -0
- package/dist/host/installed-v2.js +27181 -0
- package/dist/host/installed-v3.js +27181 -0
- package/dist/host/installed-v4.js +27181 -0
- package/dist/host/installed-v5.js +27181 -0
- package/dist/host/installed-v6.js +27181 -0
- package/dist/host/installed-v7.js +27181 -0
- package/dist/host/installed-v8.js +27181 -0
- package/dist/host/installed-v9.js +27181 -0
- package/dist/host/mcp.d.ts +11 -0
- package/dist/host/mcp.js +42 -0
- package/dist/host/mcp.js.map +1 -0
- package/dist/host/models.d.ts +37 -0
- package/dist/host/models.js +141 -0
- package/dist/host/models.js.map +1 -0
- package/dist/host/operations.d.ts +22 -0
- package/dist/host/operations.js +72 -0
- package/dist/host/operations.js.map +1 -0
- package/dist/host/plugin.js +27181 -0
- package/dist/host/policies.d.ts +36 -0
- package/dist/host/policies.js +73 -0
- package/dist/host/policies.js.map +1 -0
- package/dist/host/requests.d.ts +57 -0
- package/dist/host/requests.js +353 -0
- package/dist/host/requests.js.map +1 -0
- package/dist/host/skills.d.ts +2 -0
- package/dist/host/skills.js +45 -0
- package/dist/host/skills.js.map +1 -0
- package/dist/host/tool-client.d.ts +19 -0
- package/dist/host/tool-client.js +62 -0
- package/dist/host/tool-client.js.map +1 -0
- package/dist/host/validation.d.ts +5 -0
- package/dist/host/validation.js +42 -0
- package/dist/host/validation.js.map +1 -0
- package/dist/mcp.d.ts +7 -0
- package/dist/mcp.js +2 -0
- package/dist/mcp.js.map +1 -0
- package/dist/models.d.ts +34 -0
- package/dist/models.js +53 -0
- package/dist/models.js.map +1 -0
- package/dist/monitoring/error-buffer.d.ts +89 -0
- package/dist/monitoring/error-buffer.js +355 -0
- package/dist/monitoring/error-buffer.js.map +1 -0
- package/dist/monitoring/fatal.d.ts +6 -0
- package/dist/monitoring/fatal.js +32 -0
- package/dist/monitoring/fatal.js.map +1 -0
- package/dist/monitoring/index.d.ts +7 -0
- package/dist/monitoring/index.js +8 -0
- package/dist/monitoring/index.js.map +1 -0
- package/dist/monitoring/logger.d.ts +45 -0
- package/dist/monitoring/logger.js +174 -0
- package/dist/monitoring/logger.js.map +1 -0
- package/dist/monitoring/schemas.d.ts +34 -0
- package/dist/monitoring/schemas.js +132 -0
- package/dist/monitoring/schemas.js.map +1 -0
- package/dist/monitoring/standard.d.ts +51 -0
- package/dist/monitoring/standard.js +140 -0
- package/dist/monitoring/standard.js.map +1 -0
- package/dist/monitoring/tailer.d.ts +41 -0
- package/dist/monitoring/tailer.js +210 -0
- package/dist/monitoring/tailer.js.map +1 -0
- package/dist/requests.d.ts +92 -0
- package/dist/requests.js +120 -0
- package/dist/requests.js.map +1 -0
- package/dist/runtime/bootstrap.d.ts +1 -0
- package/dist/runtime/bootstrap.js +25 -0
- package/dist/runtime/bootstrap.js.map +1 -0
- package/dist/runtime/definitions.d.ts +7 -0
- package/dist/runtime/definitions.js +138 -0
- package/dist/runtime/definitions.js.map +1 -0
- package/dist/runtime/index.d.ts +4 -0
- package/dist/runtime/index.js +4 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/log-file.d.ts +24 -0
- package/dist/runtime/log-file.js +124 -0
- package/dist/runtime/log-file.js.map +1 -0
- package/dist/runtime/protocol.d.ts +60 -0
- package/dist/runtime/protocol.js +155 -0
- package/dist/runtime/protocol.js.map +1 -0
- package/dist/runtime/registry.d.ts +8 -0
- package/dist/runtime/registry.js +38 -0
- package/dist/runtime/registry.js.map +1 -0
- package/dist/runtime/supervisor.d.ts +74 -0
- package/dist/runtime/supervisor.js +881 -0
- package/dist/runtime/supervisor.js.map +1 -0
- package/dist/sdk/connection.d.ts +33 -0
- package/dist/sdk/connection.js +265 -0
- package/dist/sdk/connection.js.map +1 -0
- package/dist/sdk/index.d.ts +8 -0
- package/dist/sdk/index.js +2 -0
- package/dist/sdk/index.js.map +1 -0
- package/dist/supervision.d.ts +22 -0
- package/dist/supervision.js +32 -0
- package/dist/supervision.js.map +1 -0
- package/dist/types.d.ts +170 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/docs/installation.md +37 -0
- package/docs/log-format.md +102 -0
- package/docs/publishing.md +45 -0
- package/docs/supervision.md +73 -0
- package/examples/dashboard.ts +31 -0
- package/examples/dsh-web-local.patch.yml +15 -0
- package/examples/dynamic-server.ts +13 -0
- package/examples/inbox-worker.ts +17 -0
- package/examples/model-slots.ts +12 -0
- package/examples/standalone-supervisor.ts +14 -0
- package/examples/watch-server.ts +12 -0
- package/package.json +134 -3
- package/schemas/dsh.http.request.v1.schema.json +14 -0
- package/schemas/dsh.program.error-batch.v1.schema.json +24 -0
- package/schemas/dsh.program.error.v1.schema.json +8 -0
- package/schemas/standard-log-record.v1.schema.json +26 -0
- package/skills/program-agents/SKILL.md +124 -0
- package/skills/program-agents/chat-sidebar.md +23 -0
- package/skills/program-agents/endpoints.md +92 -0
- package/skills/program-agents/inboxes.md +120 -0
- package/skills/program-agents/log-format.md +102 -0
- package/skills/program-agents/models.md +59 -0
- package/skills/program-agents/references/chat-sidebar/AgentSidebar.tsx +181 -0
- package/skills/program-agents/references/chat-sidebar/README.md +181 -0
- package/skills/program-agents/references/chat-sidebar/agent-sidebar.css +37 -0
- package/skills/program-agents/references/chat-sidebar/backend.ts +184 -0
- package/skills/program-agents/references/chat-sidebar/protocol.ts +214 -0
- package/skills/program-agents/schemas/dsh.http.request.v1.schema.json +14 -0
- package/skills/program-agents/schemas/dsh.program.error-batch.v1.schema.json +24 -0
- package/skills/program-agents/schemas/dsh.program.error.v1.schema.json +8 -0
- package/skills/program-agents/schemas/standard-log-record.v1.schema.json +26 -0
- package/skills/program-agents/ui-patterns.md +39 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
window.__ModuleLoader__.load({id:"@kelloggt/dsh-program-agents",factory:(require)=>{var module={exports:{}};var exports=module.exports;
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/client/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
ProgramAgentsDock: () => ProgramAgentsDock,
|
|
25
|
+
ProgramAgentsPanel: () => ProgramAgentsPanel,
|
|
26
|
+
apply: () => apply,
|
|
27
|
+
inject: () => inject
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(index_exports);
|
|
30
|
+
|
|
31
|
+
// src/endpoints.ts
|
|
32
|
+
var controls = /[\u0000-\u0020\u007f\\]/;
|
|
33
|
+
function endpointName(value) {
|
|
34
|
+
return typeof value === "string" && /^[A-Za-z][A-Za-z0-9_-]{0,63}$/.test(value);
|
|
35
|
+
}
|
|
36
|
+
function normalizeEndpointPath(value) {
|
|
37
|
+
if (value === void 0) return "/";
|
|
38
|
+
if (typeof value !== "string" || !value.startsWith("/") || value.startsWith("//") || value.length > 4096 || controls.test(value) || /%(?:0[0-9a-f]|1[0-9a-f]|7f|5c)/i.test(value)) throw Error("Endpoint path must be a bounded absolute path, not a URL or authority");
|
|
39
|
+
const u = new URL(value, "http://endpoint.invalid");
|
|
40
|
+
if (u.origin !== "http://endpoint.invalid") throw Error("Invalid endpoint path");
|
|
41
|
+
return u.pathname + u.search + u.hash;
|
|
42
|
+
}
|
|
43
|
+
function normalizeHostname(value) {
|
|
44
|
+
if (typeof value !== "string" || !value || value.length > 253 || controls.test(value) || /[/?#@%]/.test(value)) throw Error("Invalid endpoint hostname");
|
|
45
|
+
const bracketed = value.includes(":") && !value.startsWith("[") ? "[" + value + "]" : value;
|
|
46
|
+
let u;
|
|
47
|
+
try {
|
|
48
|
+
u = new URL("http://" + bracketed + "/");
|
|
49
|
+
} catch {
|
|
50
|
+
throw Error("Invalid endpoint hostname");
|
|
51
|
+
}
|
|
52
|
+
if (u.port || u.username || u.password || u.pathname !== "/" || !u.hostname) throw Error("Hostname must not include a scheme, port or credentials");
|
|
53
|
+
return u.hostname;
|
|
54
|
+
}
|
|
55
|
+
function endpointUrl(address2, browserHostname) {
|
|
56
|
+
try {
|
|
57
|
+
if (!address2 || !endpointName(address2.endpoint) || !["http", "https"].includes(address2.protocol) || !Number.isSafeInteger(address2.port) || address2.port < 1 || address2.port > 65535) return void 0;
|
|
58
|
+
const host = normalizeHostname(address2.host === "browser" ? browserHostname : address2.host);
|
|
59
|
+
if (["0.0.0.0", "[::]", "browser"].includes(host)) return void 0;
|
|
60
|
+
const path = normalizeEndpointPath(address2.path);
|
|
61
|
+
return new URL(`${address2.protocol}://${host}:${address2.port}${path}`).href;
|
|
62
|
+
} catch {
|
|
63
|
+
return void 0;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/client/model.ts
|
|
68
|
+
var CLIENT_LIMITS = { runs: 100, programs: 100, resources: 30, textCharacters: 2e3, frameBytes: 128 * 1024 };
|
|
69
|
+
var states = ["starting", "running", "blocked", "stopping", "stopped", "completed", "failed"];
|
|
70
|
+
var isTerminal = (state) => ["stopped", "completed", "failed"].includes(state);
|
|
71
|
+
function record(value) {
|
|
72
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
73
|
+
}
|
|
74
|
+
function boundedText(value, max = CLIENT_LIMITS.textCharacters) {
|
|
75
|
+
return typeof value === "string" ? value.slice(0, max) : "";
|
|
76
|
+
}
|
|
77
|
+
function safeResourceUrl(value) {
|
|
78
|
+
if (typeof value !== "string" || value.length > 8192 || !/^https?:\/\//i.test(value) || /[\u0000-\u0020\u007f\\]/.test(value)) return void 0;
|
|
79
|
+
try {
|
|
80
|
+
const url = new URL(value);
|
|
81
|
+
return (url.protocol === "https:" || url.protocol === "http:") && url.hostname && !url.username && !url.password ? url.href : void 0;
|
|
82
|
+
} catch {
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function resourceLink(item, resource, stale = false, pending, browserHostname) {
|
|
87
|
+
if (stale || pending || !resource.available || !item.run || !["running", "blocked"].includes(item.run.state)) return void 0;
|
|
88
|
+
if (resource.endpoint) {
|
|
89
|
+
if (!resource.address || resource.address.endpoint !== resource.endpoint || resource.address.path !== (resource.path ?? "/")) return void 0;
|
|
90
|
+
return endpointUrl(resource.address, browserHostname ?? (typeof location === "undefined" ? void 0 : location.hostname));
|
|
91
|
+
}
|
|
92
|
+
return safeResourceUrl(resource.url);
|
|
93
|
+
}
|
|
94
|
+
function runControls(state, pending = false, stale = false) {
|
|
95
|
+
const ready = !pending && !stale;
|
|
96
|
+
return {
|
|
97
|
+
start: ready && (state === "configured" || isTerminal(state)),
|
|
98
|
+
stop: ready && ["starting", "running", "blocked"].includes(state),
|
|
99
|
+
restart: ready && state !== "configured" && state !== "stopping"
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function requiredString(value, name) {
|
|
103
|
+
if (typeof value !== "string" || !value || value.length > 512) throw new Error(`Invalid program snapshot ${name}.`);
|
|
104
|
+
return value;
|
|
105
|
+
}
|
|
106
|
+
var command = (value) => (Array.isArray(value) ? value : []).slice(0, 100).map((part) => boundedText(part));
|
|
107
|
+
function address(value) {
|
|
108
|
+
if (!record(value) || !endpointName(value.endpoint) || !["http", "https"].includes(String(value.protocol)) || !Number.isSafeInteger(value.port) || Number(value.port) < 1 || Number(value.port) > 65535 || typeof value.host !== "string") return void 0;
|
|
109
|
+
try {
|
|
110
|
+
const host = value.host === "browser" ? "browser" : normalizeHostname(value.host);
|
|
111
|
+
return { endpoint: value.endpoint, host, protocol: value.protocol, port: Number(value.port), path: normalizeEndpointPath(value.path) };
|
|
112
|
+
} catch {
|
|
113
|
+
return void 0;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
function resources(value) {
|
|
117
|
+
return (Array.isArray(value) ? value : []).slice(0, CLIENT_LIMITS.resources).flatMap((raw) => {
|
|
118
|
+
if (!record(raw)) return [];
|
|
119
|
+
let recipe;
|
|
120
|
+
if (raw.endpoint !== void 0) {
|
|
121
|
+
if (!endpointName(raw.endpoint)) return [];
|
|
122
|
+
try {
|
|
123
|
+
recipe = { endpoint: raw.endpoint, path: normalizeEndpointPath(raw.path) };
|
|
124
|
+
} catch {
|
|
125
|
+
return [];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
const resolved = address(raw.address);
|
|
129
|
+
return [{
|
|
130
|
+
id: boundedText(raw.id, 200),
|
|
131
|
+
title: boundedText(raw.title, 300),
|
|
132
|
+
type: ["ui", "api", "docs"].includes(String(raw.type)) ? raw.type : "ui",
|
|
133
|
+
url: boundedText(raw.url, 8192),
|
|
134
|
+
...typeof raw.docs === "string" ? { docs: boundedText(raw.docs, 8192) } : {},
|
|
135
|
+
...recipe ?? {},
|
|
136
|
+
...resolved ? { address: resolved } : {},
|
|
137
|
+
available: raw.available === true
|
|
138
|
+
}];
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
function normalizeRun(raw, sessionId) {
|
|
142
|
+
const id = requiredString(raw.id, "id");
|
|
143
|
+
if (!Number.isSafeInteger(raw.generation) || raw.generation < 1 || !states.includes(String(raw.state))) throw new Error("Invalid program snapshot state or generation.");
|
|
144
|
+
return {
|
|
145
|
+
id,
|
|
146
|
+
generation: raw.generation,
|
|
147
|
+
ownerId: sessionId,
|
|
148
|
+
program: requiredString(raw.program, "program"),
|
|
149
|
+
label: boundedText(raw.label, 200),
|
|
150
|
+
state: raw.state,
|
|
151
|
+
...typeof raw.name === "string" ? { name: boundedText(raw.name, 200) } : {},
|
|
152
|
+
...typeof raw.description === "string" ? { description: boundedText(raw.description) } : {},
|
|
153
|
+
...Array.isArray(raw.command) ? { command: command(raw.command) } : {},
|
|
154
|
+
...typeof raw.logsPath === "string" ? { logsPath: boundedText(raw.logsPath, 8192) } : {},
|
|
155
|
+
status: boundedText(raw.status, 200),
|
|
156
|
+
error: boundedText(raw.error, 240),
|
|
157
|
+
startedAt: boundedText(raw.startedAt, 100),
|
|
158
|
+
resources: resources(raw.resources ?? raw.urls)
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function parseRunList(value, sessionId) {
|
|
162
|
+
if (!record(value) || !Array.isArray(value.runs) || value.programs !== void 0 && !Array.isArray(value.programs)) throw new Error("Program agents API returned an invalid list.");
|
|
163
|
+
const scoped = value.runs.filter((raw) => record(raw) && raw.ownerId === sessionId);
|
|
164
|
+
scoped.sort((left, right) => String(right.startedAt ?? "").localeCompare(String(left.startedAt ?? "")) || Number(right.generation) - Number(left.generation));
|
|
165
|
+
const newest = /* @__PURE__ */ new Map();
|
|
166
|
+
for (const raw of scoped) if (!newest.has(String(raw.program))) newest.set(String(raw.program), raw);
|
|
167
|
+
const first = new Set(newest.values());
|
|
168
|
+
const ordered = [...first, ...scoped.filter((raw) => !first.has(raw))];
|
|
169
|
+
const runs = [];
|
|
170
|
+
const ids = /* @__PURE__ */ new Set();
|
|
171
|
+
for (const raw of ordered.slice(0, CLIENT_LIMITS.runs)) {
|
|
172
|
+
const run = normalizeRun(raw, sessionId);
|
|
173
|
+
if (!ids.has(run.id)) {
|
|
174
|
+
runs.push(run);
|
|
175
|
+
ids.add(run.id);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
const programs = [];
|
|
179
|
+
const keys = /* @__PURE__ */ new Set();
|
|
180
|
+
const definitions = Array.isArray(value.programs) ? value.programs : [];
|
|
181
|
+
for (const raw of definitions.slice(0, CLIENT_LIMITS.programs)) {
|
|
182
|
+
if (!record(raw)) throw new Error("Invalid program descriptor.");
|
|
183
|
+
const program = requiredString(raw.program, "program");
|
|
184
|
+
if (keys.has(program)) continue;
|
|
185
|
+
programs.push({ program, name: boundedText(raw.name, 200) || program, description: boundedText(raw.description), command: command(raw.command), urls: resources(raw.urls), ...raw.hasModelSlots === true ? { hasModelSlots: true, modelRevision: boundedText(raw.modelRevision, 2e3) } : {}, ...typeof raw.logsPath === "string" ? { logsPath: boundedText(raw.logsPath, 8192) } : {} });
|
|
186
|
+
keys.add(program);
|
|
187
|
+
}
|
|
188
|
+
const latest = /* @__PURE__ */ new Map();
|
|
189
|
+
for (const run of runs) if (!latest.has(run.program)) latest.set(run.program, run);
|
|
190
|
+
const items = programs.map((descriptor) => {
|
|
191
|
+
const run = latest.get(descriptor.program);
|
|
192
|
+
latest.delete(descriptor.program);
|
|
193
|
+
const merged = new Map(descriptor.urls.map((resource) => [resource.id, resource]));
|
|
194
|
+
for (const resource of run?.resources ?? []) {
|
|
195
|
+
const configured = merged.get(resource.id);
|
|
196
|
+
merged.set(resource.id, { ...resource, ...resource.docs === void 0 && configured?.docs !== void 0 ? { docs: configured.docs } : {} });
|
|
197
|
+
}
|
|
198
|
+
return { ...descriptor, urls: [...merged.values()].slice(0, CLIENT_LIMITS.resources), ...run ? { run, logsPath: run.logsPath || descriptor.logsPath } : {} };
|
|
199
|
+
});
|
|
200
|
+
for (const run of latest.values()) items.push({ program: run.program, name: run.name || run.label || run.program, description: run.description || "", command: run.command ?? [], logsPath: run.logsPath, urls: run.resources, run });
|
|
201
|
+
return { runs, programs, items: items.slice(0, CLIENT_LIMITS.programs), truncated: scoped.length > CLIENT_LIMITS.runs || definitions.length > CLIENT_LIMITS.programs || items.length > CLIENT_LIMITS.programs };
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// src/client/transport.ts
|
|
205
|
+
var ProgramApiError = class extends Error {
|
|
206
|
+
code;
|
|
207
|
+
constructor(code, message) {
|
|
208
|
+
super(message);
|
|
209
|
+
this.name = "ProgramApiError";
|
|
210
|
+
this.code = code;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
var cancelled = () => new ProgramApiError("cancelled", "Program agents request cancelled.");
|
|
214
|
+
async function abortable(operation, signal) {
|
|
215
|
+
if (signal.aborted) throw cancelled();
|
|
216
|
+
let onAbort;
|
|
217
|
+
try {
|
|
218
|
+
return await Promise.race([operation(), new Promise((_resolve, reject) => {
|
|
219
|
+
onAbort = () => reject(cancelled());
|
|
220
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
221
|
+
if (signal.aborted) onAbort();
|
|
222
|
+
})]);
|
|
223
|
+
} finally {
|
|
224
|
+
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async function consumeSnapshots(response, snapshot, signal) {
|
|
228
|
+
if (!response.ok || !/^text\/event-stream(?:\s*;|$)/i.test(response.headers.get("content-type") ?? "") || !response.body) throw new ProgramApiError("stream", "Program agents connection unavailable.");
|
|
229
|
+
const reader = response.body.getReader();
|
|
230
|
+
const decoder = new TextDecoder("utf-8", { fatal: true });
|
|
231
|
+
let line = "", event = "", data = [], frameBytes = 0;
|
|
232
|
+
const abort = () => {
|
|
233
|
+
void reader.cancel().catch(() => {
|
|
234
|
+
});
|
|
235
|
+
};
|
|
236
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
237
|
+
const completeLine = () => {
|
|
238
|
+
const text = line.endsWith("\r") ? line.slice(0, -1) : line;
|
|
239
|
+
line = "";
|
|
240
|
+
if (!text) {
|
|
241
|
+
if (event === "snapshot" && data.length) snapshot(JSON.parse(data.join("\n")));
|
|
242
|
+
event = "";
|
|
243
|
+
data = [];
|
|
244
|
+
frameBytes = 0;
|
|
245
|
+
} else if (!text.startsWith(":")) {
|
|
246
|
+
const separator = text.indexOf(":");
|
|
247
|
+
const field = separator < 0 ? text : text.slice(0, separator);
|
|
248
|
+
let value = separator < 0 ? "" : text.slice(separator + 1);
|
|
249
|
+
if (value.startsWith(" ")) value = value.slice(1);
|
|
250
|
+
if (field === "event") event = value;
|
|
251
|
+
if (field === "data") data.push(value);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
254
|
+
try {
|
|
255
|
+
while (!signal.aborted) {
|
|
256
|
+
const { done, value } = await abortable(() => reader.read(), signal);
|
|
257
|
+
if (done) break;
|
|
258
|
+
let start = 0;
|
|
259
|
+
for (let index = 0; index < value.length; index++) {
|
|
260
|
+
if (value[index] !== 10) continue;
|
|
261
|
+
frameBytes += index + 1 - start;
|
|
262
|
+
if (frameBytes > CLIENT_LIMITS.frameBytes) throw new ProgramApiError("stream", "Program agents snapshot is too large.");
|
|
263
|
+
line += decoder.decode(value.subarray(start, index), { stream: true });
|
|
264
|
+
completeLine();
|
|
265
|
+
start = index + 1;
|
|
266
|
+
}
|
|
267
|
+
frameBytes += value.length - start;
|
|
268
|
+
if (frameBytes > CLIENT_LIMITS.frameBytes) throw new ProgramApiError("stream", "Program agents snapshot is too large.");
|
|
269
|
+
line += decoder.decode(value.subarray(start), { stream: true });
|
|
270
|
+
}
|
|
271
|
+
if (signal.aborted) throw cancelled();
|
|
272
|
+
} finally {
|
|
273
|
+
signal.removeEventListener("abort", abort);
|
|
274
|
+
void reader.cancel().catch(() => {
|
|
275
|
+
});
|
|
276
|
+
reader.releaseLock();
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
function createProgramClient(connection, sessionId, streamingFetch = (input, init) => globalThis.fetch(input, init), timeoutMs = 2e4) {
|
|
280
|
+
if (!sessionId) throw new Error("Program agents require a selected Session.");
|
|
281
|
+
const rpc = "rpc" in connection ? connection.rpc : connection;
|
|
282
|
+
const call = async (method, payload, signal) => {
|
|
283
|
+
const abort = new AbortController();
|
|
284
|
+
const cancel = () => abort.abort();
|
|
285
|
+
signal.addEventListener("abort", cancel, { once: true });
|
|
286
|
+
if (signal.aborted) abort.abort();
|
|
287
|
+
const deadline = setTimeout(cancel, timeoutMs);
|
|
288
|
+
try {
|
|
289
|
+
const reply = await abortable(() => rpc.call("/api", `programAgents/${method}`, payload, abort.signal), abort.signal);
|
|
290
|
+
if (!reply.ok) throw new ProgramApiError(reply.error.code, reply.error.message);
|
|
291
|
+
return reply.value;
|
|
292
|
+
} catch (error) {
|
|
293
|
+
if (abort.signal.aborted && !signal.aborted) throw new ProgramApiError("timeout", "Program action timed out. Check status before retrying.");
|
|
294
|
+
throw error;
|
|
295
|
+
} finally {
|
|
296
|
+
clearTimeout(deadline);
|
|
297
|
+
signal.removeEventListener("abort", cancel);
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
return {
|
|
301
|
+
sessionId,
|
|
302
|
+
async modelConfiguration(program, signal) {
|
|
303
|
+
return await call("models", { sessionId, program }, signal);
|
|
304
|
+
},
|
|
305
|
+
async setModel(program, slot, selection, revision, signal) {
|
|
306
|
+
return await call("modelSet", { sessionId, program, slot, selection, revision }, signal);
|
|
307
|
+
},
|
|
308
|
+
async list(signal) {
|
|
309
|
+
return parseRunList(await call("list", { sessionId }, signal), sessionId);
|
|
310
|
+
},
|
|
311
|
+
async subscribe(snapshot, signal) {
|
|
312
|
+
const url = new URL("api/programAgents/events", document.baseURI);
|
|
313
|
+
const response = await abortable(() => streamingFetch(url, {
|
|
314
|
+
method: "POST",
|
|
315
|
+
credentials: "same-origin",
|
|
316
|
+
redirect: "error",
|
|
317
|
+
cache: "no-store",
|
|
318
|
+
headers: { "content-type": "application/json", accept: "text/event-stream" },
|
|
319
|
+
body: JSON.stringify({ sessionId }),
|
|
320
|
+
signal
|
|
321
|
+
}), signal);
|
|
322
|
+
await consumeSnapshots(response, (value) => {
|
|
323
|
+
if (!signal.aborted) snapshot(parseRunList(value, sessionId));
|
|
324
|
+
}, signal);
|
|
325
|
+
},
|
|
326
|
+
async action(action, target, signal) {
|
|
327
|
+
const run = "ownerId" in target ? target : target.run;
|
|
328
|
+
if (run && run.ownerId !== sessionId) throw new Error("Program does not belong to this Session.");
|
|
329
|
+
if (action !== "start" && !run) throw new Error("Program has no current run.");
|
|
330
|
+
await call(action, action === "start" ? { sessionId, program: target.program } : { sessionId, id: run.id, generation: run.generation }, signal);
|
|
331
|
+
}
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// src/client/view.ts
|
|
336
|
+
var import_react2 = require("react");
|
|
337
|
+
|
|
338
|
+
// src/client/controller.ts
|
|
339
|
+
var actionKey = (item) => item.program;
|
|
340
|
+
var errorText = (error) => boundedText(error instanceof Error ? error.message : "Connection unavailable.", 180);
|
|
341
|
+
function createPanelController(client, reconnectDelayMs = 1e3) {
|
|
342
|
+
let state = { runs: [], programs: [], items: [], loading: true, stale: true, truncated: false, pending: {}, actionErrors: {} };
|
|
343
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
344
|
+
let active = false, visible = true, epoch = 0, failures = 0;
|
|
345
|
+
let timer;
|
|
346
|
+
let stream;
|
|
347
|
+
const actions = /* @__PURE__ */ new Map();
|
|
348
|
+
const awaiting = /* @__PURE__ */ new Map();
|
|
349
|
+
const sameRun = (left, right) => left?.id === right?.id && left?.generation === right?.generation && left?.state === right?.state;
|
|
350
|
+
const publish = (patch) => {
|
|
351
|
+
state = { ...state, ...patch };
|
|
352
|
+
for (const listener of listeners) listener();
|
|
353
|
+
};
|
|
354
|
+
const clearTimer = () => {
|
|
355
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
356
|
+
timer = void 0;
|
|
357
|
+
};
|
|
358
|
+
const invalidate = () => {
|
|
359
|
+
epoch++;
|
|
360
|
+
clearTimer();
|
|
361
|
+
stream?.abort();
|
|
362
|
+
};
|
|
363
|
+
function connect() {
|
|
364
|
+
if (!active || !visible) return;
|
|
365
|
+
invalidate();
|
|
366
|
+
const abort = new AbortController();
|
|
367
|
+
stream = abort;
|
|
368
|
+
const current = epoch;
|
|
369
|
+
let baseline = true;
|
|
370
|
+
publish({ loading: true, stale: true });
|
|
371
|
+
const latest = () => active && visible && !abort.signal.aborted && current === epoch;
|
|
372
|
+
void (async () => {
|
|
373
|
+
try {
|
|
374
|
+
await client.subscribe((result) => {
|
|
375
|
+
if (!latest()) return;
|
|
376
|
+
failures = 0;
|
|
377
|
+
const errors = Object.fromEntries(Object.entries(state.actionErrors).filter(([key]) => {
|
|
378
|
+
const before = state.items.find((item) => item.program === key)?.run;
|
|
379
|
+
const after = result.items.find((item) => item.program === key)?.run;
|
|
380
|
+
return before?.id === after?.id && before?.generation === after?.generation;
|
|
381
|
+
}));
|
|
382
|
+
const pending = { ...state.pending };
|
|
383
|
+
for (const [key, before] of awaiting) {
|
|
384
|
+
const after = result.items.find((item) => item.program === key)?.run;
|
|
385
|
+
if (baseline || !sameRun(before, after)) {
|
|
386
|
+
awaiting.delete(key);
|
|
387
|
+
delete pending[key];
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
baseline = false;
|
|
391
|
+
publish({ ...result, loading: false, stale: false, error: void 0, actionErrors: errors, pending });
|
|
392
|
+
}, abort.signal);
|
|
393
|
+
if (latest()) throw new Error("Connection interrupted.");
|
|
394
|
+
} catch (error) {
|
|
395
|
+
if (!latest()) return;
|
|
396
|
+
publish({ loading: false, stale: true, error: errorText(error) });
|
|
397
|
+
abort.abort();
|
|
398
|
+
epoch++;
|
|
399
|
+
const delay = Math.min(3e4, Math.max(1, reconnectDelayMs) * 2 ** Math.min(failures++, 5));
|
|
400
|
+
timer = setTimeout(() => {
|
|
401
|
+
timer = void 0;
|
|
402
|
+
connect();
|
|
403
|
+
}, delay);
|
|
404
|
+
}
|
|
405
|
+
})();
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
getSnapshot: () => state,
|
|
409
|
+
subscribe(listener) {
|
|
410
|
+
listeners.add(listener);
|
|
411
|
+
return () => {
|
|
412
|
+
listeners.delete(listener);
|
|
413
|
+
};
|
|
414
|
+
},
|
|
415
|
+
start() {
|
|
416
|
+
if (active) return;
|
|
417
|
+
active = true;
|
|
418
|
+
connect();
|
|
419
|
+
},
|
|
420
|
+
stop() {
|
|
421
|
+
active = false;
|
|
422
|
+
invalidate();
|
|
423
|
+
for (const action of actions.values()) action.abort();
|
|
424
|
+
actions.clear();
|
|
425
|
+
awaiting.clear();
|
|
426
|
+
publish({ loading: false, stale: true, pending: {} });
|
|
427
|
+
},
|
|
428
|
+
setVisible(next) {
|
|
429
|
+
if (visible === next) return;
|
|
430
|
+
visible = next;
|
|
431
|
+
if (next) connect();
|
|
432
|
+
else {
|
|
433
|
+
invalidate();
|
|
434
|
+
publish({ loading: false, stale: true });
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
async action(action, target) {
|
|
438
|
+
const key = actionKey(target);
|
|
439
|
+
const latest = state.items.find((item) => item.program === target.program);
|
|
440
|
+
const expected = "ownerId" in target ? target : target.run;
|
|
441
|
+
if (!active || !visible || !latest || actions.has(key) || state.pending[key] || latest.run?.id !== expected?.id || latest.run?.generation !== expected?.generation || !runControls(latest.run?.state ?? "configured", false, state.stale)[action]) return false;
|
|
442
|
+
const abort = new AbortController();
|
|
443
|
+
actions.set(key, abort);
|
|
444
|
+
publish({ pending: { ...state.pending, [key]: action }, actionErrors: { ...state.actionErrors, [key]: "" } });
|
|
445
|
+
try {
|
|
446
|
+
await client.action(action, latest, abort.signal);
|
|
447
|
+
if (!active || abort.signal.aborted) return false;
|
|
448
|
+
const after = state.items.find((item) => item.program === key)?.run;
|
|
449
|
+
if (sameRun(expected, after)) awaiting.set(key, expected);
|
|
450
|
+
return true;
|
|
451
|
+
} catch (error) {
|
|
452
|
+
if (active && !abort.signal.aborted) {
|
|
453
|
+
publish({ stale: true, actionErrors: { ...state.actionErrors, [key]: errorText(error) } });
|
|
454
|
+
connect();
|
|
455
|
+
}
|
|
456
|
+
return false;
|
|
457
|
+
} finally {
|
|
458
|
+
if (actions.get(key) === abort) {
|
|
459
|
+
actions.delete(key);
|
|
460
|
+
const next = { ...state.pending };
|
|
461
|
+
if (!awaiting.has(key)) delete next[key];
|
|
462
|
+
if (active) publish({ pending: next });
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// src/client/models.ts
|
|
470
|
+
var import_react = require("react");
|
|
471
|
+
var control = { font: "inherit", color: "inherit", background: "var(--dsw-specific-menu, Canvas)", border: "1px solid #8885", borderRadius: 5, padding: "3px 6px", maxWidth: "100%" };
|
|
472
|
+
var routeKey = (route) => JSON.stringify([route.provider, route.model]);
|
|
473
|
+
function Slot({ slot, config, client, program, onSaved }) {
|
|
474
|
+
const [selected, setSelected] = (0, import_react.useState)(slot.selection), [pending, setPending] = (0, import_react.useState)(false), [error, setError] = (0, import_react.useState)("");
|
|
475
|
+
(0, import_react.useEffect)(() => {
|
|
476
|
+
setSelected(slot.selection);
|
|
477
|
+
setError("");
|
|
478
|
+
}, [slot.revision]);
|
|
479
|
+
const model = config.catalog.groups.find((g) => g.id === selected.provider)?.models.find((m) => m.id === selected.model), unavailable = !model;
|
|
480
|
+
const save = async (reset = false) => {
|
|
481
|
+
setPending(true);
|
|
482
|
+
setError("");
|
|
483
|
+
try {
|
|
484
|
+
await client.setModel(program, slot.id, reset ? null : selected, slot.revision, new AbortController().signal);
|
|
485
|
+
onSaved();
|
|
486
|
+
} catch (e) {
|
|
487
|
+
setError(e instanceof Error ? e.message : "Model change failed");
|
|
488
|
+
} finally {
|
|
489
|
+
setPending(false);
|
|
490
|
+
}
|
|
491
|
+
};
|
|
492
|
+
return (0, import_react.createElement)(
|
|
493
|
+
"fieldset",
|
|
494
|
+
{ style: { border: "1px solid #8884", borderRadius: 6, margin: "6px 0", padding: 8 }, disabled: pending },
|
|
495
|
+
(0, import_react.createElement)("legend", null, slot.id),
|
|
496
|
+
(0, import_react.createElement)("div", { style: { opacity: 0.8, marginBottom: 5 } }, slot.description),
|
|
497
|
+
(0, import_react.createElement)(
|
|
498
|
+
"div",
|
|
499
|
+
{ style: { display: "flex", flexWrap: "wrap", gap: 6, alignItems: "center" } },
|
|
500
|
+
(0, import_react.createElement)(
|
|
501
|
+
"label",
|
|
502
|
+
null,
|
|
503
|
+
"Model ",
|
|
504
|
+
(0, import_react.createElement)(
|
|
505
|
+
"select",
|
|
506
|
+
{ "aria-label": `Model for ${slot.id}`, style: control, value: routeKey(selected), onChange: (event) => {
|
|
507
|
+
const [provider, model2] = JSON.parse(event.target.value);
|
|
508
|
+
setSelected({ provider, model: model2 });
|
|
509
|
+
} },
|
|
510
|
+
unavailable ? (0, import_react.createElement)("option", { value: routeKey(selected) }, `${selected.provider} / ${selected.model} (unavailable)`) : null,
|
|
511
|
+
...config.catalog.groups.map((group) => (0, import_react.createElement)("optgroup", { key: group.id, label: group.name }, ...group.models.map((item) => (0, import_react.createElement)("option", { key: item.id, value: routeKey({ provider: group.id, model: item.id }) }, item.name))))
|
|
512
|
+
),
|
|
513
|
+
(0, import_react.createElement)("label", null, "Reasoning ", (0, import_react.createElement)(
|
|
514
|
+
"select",
|
|
515
|
+
{ "aria-label": `Reasoning for ${slot.id}`, style: control, value: selected.reasoningEffort ?? "", onChange: (event) => setSelected(({ provider, model: model2 }) => ({ provider, model: model2, ...event.target.value ? { reasoningEffort: event.target.value } : {} })) },
|
|
516
|
+
(0, import_react.createElement)("option", { value: "" }, "Model default"),
|
|
517
|
+
...selected.reasoningEffort && !model?.reasoning?.efforts.some((e) => e.id === selected.reasoningEffort) ? [(0, import_react.createElement)("option", { key: "unavailable", value: selected.reasoningEffort }, selected.reasoningEffort + " (unavailable)")] : [],
|
|
518
|
+
...(model?.reasoning?.efforts ?? []).map((e) => (0, import_react.createElement)("option", { key: e.id, value: e.id }, e.name))
|
|
519
|
+
))
|
|
520
|
+
),
|
|
521
|
+
(0, import_react.createElement)("button", { type: "button", style: control, onClick: () => void save(), disabled: pending || unavailable }, pending ? "Saving\u2026" : "Save"),
|
|
522
|
+
(0, import_react.createElement)("button", { type: "button", style: control, onClick: () => void save(true), disabled: pending || !slot.overridden }, "Use program default")
|
|
523
|
+
),
|
|
524
|
+
(0, import_react.createElement)("div", { style: { opacity: 0.65, fontSize: 11, marginTop: 5 } }, `Default: ${slot.default.provider} / ${slot.default.model}${slot.default.reasoningEffort ? " / " + slot.default.reasoningEffort : ""}${slot.overridden ? " \xB7 User override" : ""}`),
|
|
525
|
+
unavailable ? (0, import_react.createElement)("p", { role: "status" }, "Saved model is unavailable. It will not silently fall back.") : null,
|
|
526
|
+
error ? (0, import_react.createElement)("p", { role: "alert" }, error) : null
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
function ProgramModels({ client, program, revision }) {
|
|
530
|
+
const [config, setConfig] = (0, import_react.useState)(), [error, setError] = (0, import_react.useState)(""), [refresh, setRefresh] = (0, import_react.useState)(0);
|
|
531
|
+
(0, import_react.useEffect)(() => {
|
|
532
|
+
const abort = new AbortController();
|
|
533
|
+
setError("");
|
|
534
|
+
void client.modelConfiguration(program, abort.signal).then((value) => {
|
|
535
|
+
if (!abort.signal.aborted) setConfig(value);
|
|
536
|
+
}, (e) => {
|
|
537
|
+
if (!abort.signal.aborted) setError(e instanceof Error ? e.message : "Model configuration unavailable");
|
|
538
|
+
});
|
|
539
|
+
return () => abort.abort();
|
|
540
|
+
}, [client, program, revision, refresh]);
|
|
541
|
+
return (0, import_react.createElement)(
|
|
542
|
+
"div",
|
|
543
|
+
{ "aria-label": "Program model configuration", style: { marginTop: 6 } },
|
|
544
|
+
(0, import_react.createElement)("p", { style: { opacity: 0.75 } }, "Selections affect future model-slot calls and newly created conversations. Existing conversations retain their model."),
|
|
545
|
+
error ? (0, import_react.createElement)("p", { role: "alert" }, error) : null,
|
|
546
|
+
!config && !error ? (0, import_react.createElement)("p", null, "Loading models\u2026") : null,
|
|
547
|
+
...(config?.slots ?? []).map((slot) => (0, import_react.createElement)(Slot, { key: slot.id, slot, config, client, program, onSaved: () => setRefresh((n) => n + 1) })),
|
|
548
|
+
...(config?.catalog.failures ?? []).map((f) => (0, import_react.createElement)("p", { key: f.id, role: "status" }, `${f.name}: ${f.message}`))
|
|
549
|
+
);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// src/client/view.ts
|
|
553
|
+
var panelStyle = {
|
|
554
|
+
boxSizing: "border-box",
|
|
555
|
+
width: "100%",
|
|
556
|
+
maxWidth: "var(--dsh-composer-card-max-width, 960px)",
|
|
557
|
+
margin: "0 auto 4px",
|
|
558
|
+
border: "1px solid var(--dsw-alias-border-l1, #8884)",
|
|
559
|
+
borderRadius: 8,
|
|
560
|
+
background: "var(--dsw-specific-menu, Canvas)",
|
|
561
|
+
color: "var(--dsw-alias-label-primary, CanvasText)",
|
|
562
|
+
fontSize: 12
|
|
563
|
+
};
|
|
564
|
+
var rowStyle = { display: "flex", gap: 8, alignItems: "center", minWidth: 0 };
|
|
565
|
+
var buttonStyle = { font: "inherit", color: "inherit", background: "transparent", border: "1px solid var(--dsw-alias-border-l1, #8885)", borderRadius: 5, padding: "2px 6px", cursor: "pointer", whiteSpace: "nowrap" };
|
|
566
|
+
var mutedStyle = { opacity: 0.65, fontSize: 11 };
|
|
567
|
+
var linkStyle = { color: "var(--dsw-alias-link-primary, LinkText)", whiteSpace: "nowrap" };
|
|
568
|
+
var errorStyle = { color: "var(--dsw-alias-state-error-primary, #bc3333)", fontSize: 11, margin: "3px 0 0", overflowWrap: "anywhere" };
|
|
569
|
+
function button(label, disabled, onClick, extra = {}) {
|
|
570
|
+
return (0, import_react2.createElement)("button", { type: "button", style: { ...buttonStyle, opacity: disabled ? 0.5 : 1 }, disabled, onClick, ...extra }, label);
|
|
571
|
+
}
|
|
572
|
+
function ProgramRow({ item, state, controller, client }) {
|
|
573
|
+
const key = actionKey(item), pending = state.pending[key];
|
|
574
|
+
const status = item.run?.state ?? "configured";
|
|
575
|
+
const controls2 = runControls(status, Boolean(pending), state.stale);
|
|
576
|
+
const [more, setMore] = (0, import_react2.useState)(false);
|
|
577
|
+
const [models, setModels] = (0, import_react2.useState)(false);
|
|
578
|
+
const [copied, setCopied] = (0, import_react2.useState)("");
|
|
579
|
+
const localDocs = [...new Set(item.urls.flatMap((resource) => [
|
|
580
|
+
...resource.docs && !safeResourceUrl(resource.docs) ? [resource.docs] : [],
|
|
581
|
+
...resource.type === "docs" && /^(?:\/|[A-Za-z]:[\\/])/.test(resource.url) && !safeResourceUrl(resource.url) ? [resource.url] : []
|
|
582
|
+
]))];
|
|
583
|
+
const hasMore = Boolean(item.description || item.command.length || item.logsPath || localDocs.length);
|
|
584
|
+
const perform = (action) => {
|
|
585
|
+
void controller.action(action, item);
|
|
586
|
+
};
|
|
587
|
+
const copy = (text) => {
|
|
588
|
+
if (typeof navigator.clipboard?.writeText === "function") void navigator.clipboard.writeText(text).then(() => setCopied(text), () => {
|
|
589
|
+
});
|
|
590
|
+
};
|
|
591
|
+
return (0, import_react2.createElement)(
|
|
592
|
+
"article",
|
|
593
|
+
{ "data-program-key": item.program, style: { padding: "6px 9px", borderTop: "1px solid var(--dsw-alias-border-l1, #8883)" } },
|
|
594
|
+
(0, import_react2.createElement)(
|
|
595
|
+
"div",
|
|
596
|
+
{ style: rowStyle },
|
|
597
|
+
(0, import_react2.createElement)("strong", { title: item.name, style: { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap", flex: "1 1 100px" } }, item.name),
|
|
598
|
+
(0, import_react2.createElement)("span", { "data-program-state": status, title: item.run?.status, style: mutedStyle }, status),
|
|
599
|
+
(0, import_react2.createElement)("span", { "aria-label": "Program resources", style: { ...rowStyle, gap: 6, flexWrap: "wrap" } }, ...item.urls.flatMap((resource, index) => {
|
|
600
|
+
const href = resourceLink(item, resource, state.stale, pending);
|
|
601
|
+
const label = resource.type.toUpperCase();
|
|
602
|
+
const docsHref = resource.docs && safeResourceUrl(resource.docs);
|
|
603
|
+
return [
|
|
604
|
+
href ? (0, import_react2.createElement)("a", { key: `${index}:url`, href, target: "_blank", rel: "noopener noreferrer", referrerPolicy: "no-referrer", title: resource.title, style: linkStyle }, label) : (0, import_react2.createElement)("span", { key: `${index}:url`, "aria-disabled": true, title: resource.title, style: mutedStyle }, label),
|
|
605
|
+
...docsHref && resource.type !== "docs" ? [href ? (0, import_react2.createElement)("a", { key: `${index}:docs`, href: docsHref, target: "_blank", rel: "noopener noreferrer", referrerPolicy: "no-referrer", style: linkStyle }, "Docs") : (0, import_react2.createElement)("span", { key: `${index}:docs`, "aria-disabled": true, style: mutedStyle }, "Docs")] : []
|
|
606
|
+
];
|
|
607
|
+
})),
|
|
608
|
+
status === "configured" || ["stopped", "completed", "failed"].includes(status) ? button(pending === "start" ? "Starting\u2026" : "Start", !controls2.start, () => perform("start")) : null,
|
|
609
|
+
status !== "configured" && !["stopped", "completed", "failed"].includes(status) ? button(pending === "stop" ? "Stopping\u2026" : "Stop", !controls2.stop, () => perform("stop")) : null,
|
|
610
|
+
status !== "configured" ? button(pending === "restart" ? "Restarting\u2026" : "Restart", !controls2.restart, () => perform("restart")) : null,
|
|
611
|
+
item.hasModelSlots && client.modelConfiguration && client.setModel ? button("Models", state.stale, () => setModels(!models), { "aria-expanded": models, "aria-label": `Configure models for ${item.name}` }) : null,
|
|
612
|
+
hasMore ? button("More", false, () => setMore(!more), { "aria-expanded": more, "aria-label": `More about ${item.name}`, style: { ...buttonStyle, border: 0, padding: "2px 0", ...mutedStyle } }) : null
|
|
613
|
+
),
|
|
614
|
+
state.actionErrors[key] || item.run?.error ? (0, import_react2.createElement)("p", { role: "alert", style: errorStyle }, state.actionErrors[key] || item.run?.error) : null,
|
|
615
|
+
models && item.hasModelSlots ? (0, import_react2.createElement)(ProgramModels, { client, program: item.program, revision: item.modelRevision }) : null,
|
|
616
|
+
more ? (0, import_react2.createElement)(
|
|
617
|
+
"div",
|
|
618
|
+
{ "aria-label": `${item.name} details`, style: { ...mutedStyle, marginTop: 5, overflowWrap: "anywhere" } },
|
|
619
|
+
item.description ? (0, import_react2.createElement)("div", null, item.description) : null,
|
|
620
|
+
item.command.length ? (0, import_react2.createElement)("div", null, (0, import_react2.createElement)("code", null, item.command.join(" "))) : null,
|
|
621
|
+
item.logsPath ? (0, import_react2.createElement)("div", null, "Logs: ", (0, import_react2.createElement)("code", null, item.logsPath)) : null,
|
|
622
|
+
...localDocs.map((path, index) => (0, import_react2.createElement)("div", { key: index }, "Docs: ", (0, import_react2.createElement)("code", null, path), " ", button(copied === path ? "Copied" : "Copy", false, () => copy(path))))
|
|
623
|
+
) : null
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
function ProgramAgentsPanel({ client, reconnectDelayMs }) {
|
|
627
|
+
const controller = (0, import_react2.useMemo)(() => createPanelController(client, reconnectDelayMs), [client, reconnectDelayMs]);
|
|
628
|
+
const state = (0, import_react2.useSyncExternalStore)(controller.subscribe, controller.getSnapshot, controller.getSnapshot);
|
|
629
|
+
(0, import_react2.useEffect)(() => {
|
|
630
|
+
const changed = () => controller.setVisible(document.visibilityState !== "hidden");
|
|
631
|
+
changed();
|
|
632
|
+
controller.start();
|
|
633
|
+
document.addEventListener("visibilitychange", changed);
|
|
634
|
+
return () => {
|
|
635
|
+
document.removeEventListener("visibilitychange", changed);
|
|
636
|
+
controller.stop();
|
|
637
|
+
};
|
|
638
|
+
}, [controller]);
|
|
639
|
+
const [open, setOpen] = (0, import_react2.useState)(false);
|
|
640
|
+
return (0, import_react2.createElement)(
|
|
641
|
+
"section",
|
|
642
|
+
{ "aria-label": "Program agents", "data-session-id": client.sessionId, style: panelStyle },
|
|
643
|
+
(0, import_react2.createElement)(
|
|
644
|
+
"div",
|
|
645
|
+
{ style: { ...rowStyle, padding: "3px 8px" } },
|
|
646
|
+
button(`${open ? "\u25BE" : "\u25B8"} Program agents (${state.items.length})`, false, () => setOpen(!open), { "aria-expanded": open, style: { ...buttonStyle, border: 0, flex: 1, textAlign: "left", padding: "2px 0" } }),
|
|
647
|
+
state.stale ? (0, import_react2.createElement)("span", { role: "status", title: state.error, style: mutedStyle }, state.error ? "Reconnecting\u2026" : state.loading ? "Connecting\u2026" : "Paused") : null
|
|
648
|
+
),
|
|
649
|
+
open ? (0, import_react2.createElement)(
|
|
650
|
+
"div",
|
|
651
|
+
{ "data-program-foldout": true, style: { maxHeight: "min(240px, 35vh)", overflowY: "auto", overscrollBehavior: "contain" } },
|
|
652
|
+
!state.items.length && !state.loading ? (0, import_react2.createElement)("div", { style: { padding: "5px 9px", ...mutedStyle } }, "No programs configured.") : null,
|
|
653
|
+
state.truncated ? (0, import_react2.createElement)("div", { style: { padding: "3px 9px", ...mutedStyle } }, "Showing the most recent programs.") : null,
|
|
654
|
+
...state.items.map((item) => (0, import_react2.createElement)(ProgramRow, { key: item.program, item, state, controller, client }))
|
|
655
|
+
) : null
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
function ProgramAgentsDock({ programClient }) {
|
|
659
|
+
return (0, import_react2.createElement)(ProgramAgentsPanel, { key: programClient.sessionId, client: programClient });
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// src/client/index.ts
|
|
663
|
+
var inject = ["slots", "connection"];
|
|
664
|
+
function apply(ctx) {
|
|
665
|
+
ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
|
|
666
|
+
name: "conversation.input.dock",
|
|
667
|
+
id: "program-agents",
|
|
668
|
+
order: 30,
|
|
669
|
+
inject: (sessionId) => ({ programClient: createProgramClient(ctx.connection, sessionId) })
|
|
670
|
+
}, ProgramAgentsDock));
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
return module.exports;}});
|