@songsid/agend 2.1.5 → 2.1.6-beta.10
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/dist/agent-endpoint.d.ts +18 -0
- package/dist/agent-endpoint.js +53 -2
- package/dist/agent-endpoint.js.map +1 -1
- package/dist/apply-job.d.ts +114 -0
- package/dist/apply-job.js +214 -0
- package/dist/apply-job.js.map +1 -0
- package/dist/backend/claude-code.d.ts +25 -0
- package/dist/backend/claude-code.js +127 -0
- package/dist/backend/claude-code.js.map +1 -1
- package/dist/backend/codex.d.ts +57 -0
- package/dist/backend/codex.js +183 -0
- package/dist/backend/codex.js.map +1 -1
- package/dist/backend/credential-profile.d.ts +159 -0
- package/dist/backend/credential-profile.js +340 -0
- package/dist/backend/credential-profile.js.map +1 -0
- package/dist/backend/factory.js +4 -1
- package/dist/backend/factory.js.map +1 -1
- package/dist/backend/kiro-auth-store.d.ts +25 -0
- package/dist/backend/kiro-auth-store.js +61 -0
- package/dist/backend/kiro-auth-store.js.map +1 -0
- package/dist/backend/kiro.d.ts +8 -0
- package/dist/backend/kiro.js +31 -2
- package/dist/backend/kiro.js.map +1 -1
- package/dist/backend/muse.d.ts +90 -0
- package/dist/backend/muse.js +437 -0
- package/dist/backend/muse.js.map +1 -0
- package/dist/backend/types.d.ts +54 -0
- package/dist/backend/types.js.map +1 -1
- package/dist/channel/access-manager.d.ts +20 -0
- package/dist/channel/access-manager.js +25 -0
- package/dist/channel/access-manager.js.map +1 -1
- package/dist/channel/adapters/discord.d.ts +17 -0
- package/dist/channel/adapters/discord.js +72 -11
- package/dist/channel/adapters/discord.js.map +1 -1
- package/dist/channel/adapters/telegram.d.ts +6 -0
- package/dist/channel/adapters/telegram.js +36 -0
- package/dist/channel/adapters/telegram.js.map +1 -1
- package/dist/channel/markdown-chunk.d.ts +8 -3
- package/dist/channel/markdown-chunk.js +12 -3
- package/dist/channel/markdown-chunk.js.map +1 -1
- package/dist/channel/mcp-server.js +9 -12
- package/dist/channel/mcp-server.js.map +1 -1
- package/dist/channel/mcp-tools.d.ts +6 -2
- package/dist/channel/mcp-tools.js +6 -36
- package/dist/channel/mcp-tools.js.map +1 -1
- package/dist/channel/tool-router.js +32 -3
- package/dist/channel/tool-router.js.map +1 -1
- package/dist/channel/types.d.ts +18 -0
- package/dist/cli.js +113 -0
- package/dist/cli.js.map +1 -1
- package/dist/config-validator.js +45 -5
- package/dist/config-validator.js.map +1 -1
- package/dist/connection-secrets.d.ts +96 -0
- package/dist/connection-secrets.js +25 -0
- package/dist/connection-secrets.js.map +1 -0
- package/dist/daemon.d.ts +76 -0
- package/dist/daemon.js +489 -103
- package/dist/daemon.js.map +1 -1
- package/dist/event-log.d.ts +9 -0
- package/dist/event-log.js +21 -0
- package/dist/event-log.js.map +1 -1
- package/dist/fleet-level-config.d.ts +42 -0
- package/dist/fleet-level-config.js +78 -0
- package/dist/fleet-level-config.js.map +1 -0
- package/dist/fleet-lock.d.ts +21 -0
- package/dist/fleet-lock.js +42 -8
- package/dist/fleet-lock.js.map +1 -1
- package/dist/fleet-manager.d.ts +425 -2
- package/dist/fleet-manager.js +2055 -182
- package/dist/fleet-manager.js.map +1 -1
- package/dist/general-knowledge/skills/credential-profiles/SKILL.md +145 -0
- package/dist/general-knowledge/skills/worker-collaboration/SKILL.md +11 -1
- package/dist/instance-config-impact.d.ts +55 -0
- package/dist/instance-config-impact.js +152 -0
- package/dist/instance-config-impact.js.map +1 -0
- package/dist/instance-lifecycle.js +8 -4
- package/dist/instance-lifecycle.js.map +1 -1
- package/dist/locale.js +24 -0
- package/dist/locale.js.map +1 -1
- package/dist/logger.js +42 -25
- package/dist/logger.js.map +1 -1
- package/dist/muse-usage-relay.d.ts +72 -0
- package/dist/muse-usage-relay.js +419 -0
- package/dist/muse-usage-relay.js.map +1 -0
- package/dist/outbound-handlers.d.ts +5 -1
- package/dist/outbound-handlers.js +199 -1
- package/dist/outbound-handlers.js.map +1 -1
- package/dist/outbound-schemas.d.ts +7 -5
- package/dist/outbound-schemas.js +3 -2
- package/dist/outbound-schemas.js.map +1 -1
- package/dist/provider-probe.d.ts +48 -0
- package/dist/provider-probe.js +121 -0
- package/dist/provider-probe.js.map +1 -0
- package/dist/provider-secret-registry.d.ts +98 -0
- package/dist/provider-secret-registry.js +334 -0
- package/dist/provider-secret-registry.js.map +1 -0
- package/dist/quickstart-api.d.ts +164 -0
- package/dist/quickstart-api.js +351 -0
- package/dist/quickstart-api.js.map +1 -0
- package/dist/quickstart.js +23 -50
- package/dist/quickstart.js.map +1 -1
- package/dist/scheduler/db.js +17 -5
- package/dist/scheduler/db.js.map +1 -1
- package/dist/scheduler/db.test.js +34 -1
- package/dist/scheduler/db.test.js.map +1 -1
- package/dist/scheduler/types.d.ts +4 -0
- package/dist/scheduler/types.js.map +1 -1
- package/dist/secret-store.d.ts +25 -0
- package/dist/secret-store.js +165 -0
- package/dist/secret-store.js.map +1 -0
- package/dist/self-restart-limit.d.ts +21 -0
- package/dist/self-restart-limit.js +129 -0
- package/dist/self-restart-limit.js.map +1 -0
- package/dist/settings-api.d.ts +116 -0
- package/dist/settings-api.js +530 -5
- package/dist/settings-api.js.map +1 -1
- package/dist/setup-auth.d.ts +113 -0
- package/dist/setup-auth.js +181 -0
- package/dist/setup-auth.js.map +1 -0
- package/dist/setup-form.d.ts +18 -0
- package/dist/setup-form.js +315 -0
- package/dist/setup-form.js.map +1 -0
- package/dist/setup-host.d.ts +162 -0
- package/dist/setup-host.js +496 -0
- package/dist/setup-host.js.map +1 -0
- package/dist/setup-marker.d.ts +8 -0
- package/dist/setup-marker.js +39 -0
- package/dist/setup-marker.js.map +1 -0
- package/dist/setup-tunnel-consent.d.ts +53 -0
- package/dist/setup-tunnel-consent.js +82 -0
- package/dist/setup-tunnel-consent.js.map +1 -0
- package/dist/setup-wizard.js +4 -0
- package/dist/setup-wizard.js.map +1 -1
- package/dist/steer-capability.js +4 -1
- package/dist/steer-capability.js.map +1 -1
- package/dist/tips.js +1 -1
- package/dist/tips.js.map +1 -1
- package/dist/tmux-manager.js +29 -4
- package/dist/tmux-manager.js.map +1 -1
- package/dist/tool-permissions-notice.d.ts +38 -0
- package/dist/tool-permissions-notice.js +92 -0
- package/dist/tool-permissions-notice.js.map +1 -0
- package/dist/tool-permissions.d.ts +120 -0
- package/dist/tool-permissions.js +277 -0
- package/dist/tool-permissions.js.map +1 -0
- package/dist/topic-commands.js +1 -0
- package/dist/topic-commands.js.map +1 -1
- package/dist/transcript-sources.d.ts +12 -1
- package/dist/transcript-sources.js +19 -3
- package/dist/transcript-sources.js.map +1 -1
- package/dist/tunnel/cloudflared.d.ts +71 -0
- package/dist/tunnel/cloudflared.js +447 -0
- package/dist/tunnel/cloudflared.js.map +1 -0
- package/dist/tunnel/lease.d.ts +75 -0
- package/dist/tunnel/lease.js +209 -0
- package/dist/tunnel/lease.js.map +1 -0
- package/dist/tunnel/manager.d.ts +56 -0
- package/dist/tunnel/manager.js +167 -0
- package/dist/tunnel/manager.js.map +1 -0
- package/dist/tunnel/types.d.ts +119 -0
- package/dist/tunnel/types.js +33 -0
- package/dist/tunnel/types.js.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/ui/dashboard.html +4 -3
- package/dist/ui/settings.html +808 -156
- package/dist/ui/view.html +2 -2
- package/dist/usage/i18n-keys.d.ts +1 -1
- package/dist/usage/i18n-keys.js +1 -1
- package/dist/usage/i18n-keys.js.map +1 -1
- package/dist/usage/providers.d.ts +23 -14
- package/dist/usage/providers.js +220 -58
- package/dist/usage/providers.js.map +1 -1
- package/dist/usage/usage-api.d.ts +13 -0
- package/dist/usage/usage-api.js +69 -1
- package/dist/usage/usage-api.js.map +1 -1
- package/dist/web-api.js +8 -5
- package/dist/web-api.js.map +1 -1
- package/dist/web-auth.d.ts +75 -0
- package/dist/web-auth.js +208 -2
- package/dist/web-auth.js.map +1 -1
- package/dist/web-terminal.d.ts +30 -1
- package/dist/web-terminal.js +86 -3
- package/dist/web-terminal.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pre-fleet form, inline.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately not `ui/settings.html`: that page is a panel for a running
|
|
5
|
+
* fleet — it starts and stops agents, applies jobs, restarts AgEnD. Serving it
|
|
6
|
+
* from a host that has none of those would be a page of controls that cannot
|
|
7
|
+
* work, and it would put the whole panel on a surface that exists before any
|
|
8
|
+
* fleet-level access control does.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* What an unauthenticated visitor gets: a box to type the code into.
|
|
12
|
+
*
|
|
13
|
+
* Deliberately says nothing about this machine — no backend list, no existing
|
|
14
|
+
* channels, no hostname. Whoever has the link has only the link, and until they
|
|
15
|
+
* prove they also have the code they learn nothing from it.
|
|
16
|
+
*/
|
|
17
|
+
export const SETUP_CODE_PAGE_HTML = `<!DOCTYPE html>
|
|
18
|
+
<html lang="en">
|
|
19
|
+
<head>
|
|
20
|
+
<meta charset="utf-8">
|
|
21
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
22
|
+
<!-- Proof that a request reached THIS listener and not merely something that
|
|
23
|
+
answers 200. The tunnel's readiness probe looks for exactly this. It is the
|
|
24
|
+
sid, which is already in the URL, so it discloses nothing new. -->
|
|
25
|
+
<meta name="agend-setup" content="__AGEND_SETUP_MARKER__">
|
|
26
|
+
<title>Set up AgEnD</title>
|
|
27
|
+
<style>
|
|
28
|
+
body { font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 0; background: #ffffff; color: #111827; }
|
|
29
|
+
main { max-width: 420px; margin: 0 auto; padding: 64px 20px; }
|
|
30
|
+
h1 { font-size: 20px; margin: 0 0 8px; }
|
|
31
|
+
p { color: #6b7280; font-size: 13px; margin: 0 0 20px; }
|
|
32
|
+
input { width: 100%; padding: 12px; border: 1px solid #e5e7eb; border-radius: 6px; font-size: 20px; letter-spacing: 3px; text-align: center; text-transform: uppercase; box-sizing: border-box; }
|
|
33
|
+
button { width: 100%; margin-top: 12px; padding: 12px; border: 1px solid #2563eb; border-radius: 6px; background: #2563eb; color: #fff; font-size: 15px; cursor: pointer; }
|
|
34
|
+
button:disabled { opacity: .5; cursor: default; }
|
|
35
|
+
.err { color: #dc2626; font-size: 13px; margin-top: 12px; min-height: 18px; }
|
|
36
|
+
</style>
|
|
37
|
+
</head>
|
|
38
|
+
<body>
|
|
39
|
+
<main>
|
|
40
|
+
<h1>Set up AgEnD</h1>
|
|
41
|
+
<p>Enter the setup code shown in the terminal where you ran <code>agend setup</code>.</p>
|
|
42
|
+
<form id="f">
|
|
43
|
+
<input id="code" autocomplete="off" autocapitalize="characters" spellcheck="false" placeholder="ABCD-EFGH" maxlength="12" autofocus>
|
|
44
|
+
<button id="go" type="submit">Continue</button>
|
|
45
|
+
</form>
|
|
46
|
+
<div class="err" id="msg"></div>
|
|
47
|
+
</main>
|
|
48
|
+
<script>
|
|
49
|
+
(() => {
|
|
50
|
+
"use strict";
|
|
51
|
+
const msg = document.getElementById("msg");
|
|
52
|
+
const go = document.getElementById("go");
|
|
53
|
+
document.getElementById("f").addEventListener("submit", async (event) => {
|
|
54
|
+
event.preventDefault();
|
|
55
|
+
go.disabled = true;
|
|
56
|
+
msg.textContent = "";
|
|
57
|
+
let res;
|
|
58
|
+
try {
|
|
59
|
+
res = await fetch("open", {
|
|
60
|
+
method: "POST",
|
|
61
|
+
headers: { "Content-Type": "application/json" },
|
|
62
|
+
body: JSON.stringify({ code: document.getElementById("code").value }),
|
|
63
|
+
});
|
|
64
|
+
} catch {
|
|
65
|
+
msg.textContent = "Could not reach the setup page.";
|
|
66
|
+
go.disabled = false;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (res.ok) { location.reload(); return; }
|
|
70
|
+
let body = null; try { body = await res.json(); } catch {}
|
|
71
|
+
msg.textContent = (body && body.error) || "That code was not accepted.";
|
|
72
|
+
// A spent budget ends the page; there is nothing useful left to type into.
|
|
73
|
+
if (res.status !== 410) go.disabled = false;
|
|
74
|
+
});
|
|
75
|
+
})();
|
|
76
|
+
</script>
|
|
77
|
+
</body>
|
|
78
|
+
</html>
|
|
79
|
+
`;
|
|
80
|
+
export const SETUP_FORM_HTML = `<!DOCTYPE html>
|
|
81
|
+
<html lang="en">
|
|
82
|
+
<head>
|
|
83
|
+
<meta charset="utf-8">
|
|
84
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
85
|
+
<title>Set up AgEnD</title>
|
|
86
|
+
<style>
|
|
87
|
+
* { box-sizing: border-box; }
|
|
88
|
+
body { font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; margin: 0; background: #ffffff; color: #111827; }
|
|
89
|
+
main { max-width: 640px; margin: 0 auto; padding: 32px 20px 64px; }
|
|
90
|
+
h1 { font-size: 20px; margin: 0 0 4px; }
|
|
91
|
+
.sub { color: #6b7280; font-size: 13px; }
|
|
92
|
+
.step { border: 1px solid #e5e7eb; border-radius: 8px; padding: 16px; margin-top: 16px; }
|
|
93
|
+
.step h2 { font-size: 14px; margin: 0 0 12px; }
|
|
94
|
+
label { display: block; font-size: 13px; font-weight: 600; margin: 12px 0 4px; }
|
|
95
|
+
input, select { width: 100%; padding: 8px 10px; border: 1px solid #e5e7eb; border-radius: 6px; font-size: 14px; }
|
|
96
|
+
button { padding: 8px 14px; border: 1px solid #e5e7eb; border-radius: 6px; background: #fff; font-size: 14px; cursor: pointer; }
|
|
97
|
+
button.primary { background: #2563eb; color: #fff; border-color: #2563eb; }
|
|
98
|
+
button:disabled { opacity: .5; cursor: default; }
|
|
99
|
+
.row { display: flex; gap: 8px; align-items: center; margin-top: 12px; flex-wrap: wrap; }
|
|
100
|
+
.msg { font-size: 13px; }
|
|
101
|
+
.ok { color: #059669; } .err { color: #dc2626; }
|
|
102
|
+
pre { background: #f9fafb; border: 1px solid #e5e7eb; border-radius: 6px; padding: 12px; font-size: 12px; overflow: auto; }
|
|
103
|
+
</style>
|
|
104
|
+
</head>
|
|
105
|
+
<body>
|
|
106
|
+
<main>
|
|
107
|
+
<h1>Set up AgEnD</h1>
|
|
108
|
+
<div class="sub" id="intro">This page is open only while setup is running. It closes itself when you finish, or after 15 minutes.</div>
|
|
109
|
+
|
|
110
|
+
<div class="step">
|
|
111
|
+
<h2>1 · Agent</h2>
|
|
112
|
+
<label for="backend">Backend</label>
|
|
113
|
+
<select id="backend"></select>
|
|
114
|
+
<div class="sub" id="backendHint"></div>
|
|
115
|
+
<label for="dir">Working directory (absolute path)</label>
|
|
116
|
+
<input id="dir" placeholder="/home/you/projects/app">
|
|
117
|
+
<label for="name">First agent name</label>
|
|
118
|
+
<input id="name" value="agent-1">
|
|
119
|
+
</div>
|
|
120
|
+
|
|
121
|
+
<div class="step">
|
|
122
|
+
<h2>2 · Platform</h2>
|
|
123
|
+
<div class="row">
|
|
124
|
+
<button id="pickTelegram" class="primary">Telegram</button>
|
|
125
|
+
<button id="pickDiscord">Discord</button>
|
|
126
|
+
</div>
|
|
127
|
+
<div class="sub" id="platformHint">Create a bot with @BotFather, then add it to a group.</div>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<div class="step">
|
|
131
|
+
<h2>3 · Credentials</h2>
|
|
132
|
+
<label for="token">Bot token</label>
|
|
133
|
+
<input id="token" type="password" placeholder="123456:ABC-DEF…">
|
|
134
|
+
<div class="sub">Written to ~/.agend/.env and never shown again.</div>
|
|
135
|
+
<label for="tokenEnv">Environment variable</label>
|
|
136
|
+
<input id="tokenEnv" value="AGEND_BOT_TOKEN">
|
|
137
|
+
<div class="row"><button id="verify">Verify</button><span class="msg" id="verifyMsg"></span></div>
|
|
138
|
+
<div id="platformFields"></div>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<div class="step">
|
|
142
|
+
<h2>4 · Review and start</h2>
|
|
143
|
+
<div class="row"><button id="preview">Show what will be written</button></div>
|
|
144
|
+
<pre id="planOut" hidden></pre>
|
|
145
|
+
<div class="row"><button id="finish" class="primary" disabled>Create and start AgEnD</button><span class="msg" id="finishMsg"></span></div>
|
|
146
|
+
</div>
|
|
147
|
+
</main>
|
|
148
|
+
<script>
|
|
149
|
+
(() => {
|
|
150
|
+
"use strict";
|
|
151
|
+
const $ = (id) => document.getElementById(id);
|
|
152
|
+
const state = { platform: "telegram", identity: null, guilds: [], offset: 0, plan: null };
|
|
153
|
+
// Relative to the page's own directory, which is /s/<sid>/. An absolute path
|
|
154
|
+
// would leave that namespace and 404 — the sid is where this page lives, not
|
|
155
|
+
// a prefix bolted on to it.
|
|
156
|
+
const api = async (path, opts) => {
|
|
157
|
+
const res = await fetch(path, Object.assign({ headers: { "Content-Type": "application/json" } }, opts || {}));
|
|
158
|
+
let body = null; try { body = await res.json(); } catch {}
|
|
159
|
+
return { ok: res.ok, status: res.status, body };
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
async function loadEnvironment() {
|
|
163
|
+
const res = await api("api/settings/quickstart/environment");
|
|
164
|
+
const backends = res.body?.backends || [];
|
|
165
|
+
$("backend").innerHTML = "";
|
|
166
|
+
for (const name of backends.length ? backends : ["claude-code"]) {
|
|
167
|
+
const opt = document.createElement("option"); opt.value = name; opt.textContent = name; $("backend").append(opt);
|
|
168
|
+
}
|
|
169
|
+
$("backendHint").textContent = backends.length
|
|
170
|
+
? "Found on this host: " + backends.join(", ")
|
|
171
|
+
: "No backend CLI found on this host — install one first.";
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function setPlatform(platform) {
|
|
175
|
+
state.platform = platform;
|
|
176
|
+
state.identity = null; $("verifyMsg").textContent = "";
|
|
177
|
+
$("pickTelegram").className = platform === "telegram" ? "primary" : "";
|
|
178
|
+
$("pickDiscord").className = platform === "discord" ? "primary" : "";
|
|
179
|
+
$("platformHint").textContent = platform === "telegram"
|
|
180
|
+
? "Create a bot with @BotFather, then add it to a group."
|
|
181
|
+
: "Create an application in the Discord developer portal and invite the bot to your server.";
|
|
182
|
+
renderPlatformFields();
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Only the ids that platform has — the same question the CLI asks.
|
|
186
|
+
function renderPlatformFields() {
|
|
187
|
+
const host = $("platformFields"); host.innerHTML = "";
|
|
188
|
+
const field = (id, labelText, hint) => {
|
|
189
|
+
const label = document.createElement("label"); label.textContent = labelText; label.htmlFor = id;
|
|
190
|
+
const input = document.createElement("input"); input.id = id;
|
|
191
|
+
host.append(label, input);
|
|
192
|
+
if (hint) { const h = document.createElement("div"); h.className = "sub"; h.textContent = hint; host.append(h); }
|
|
193
|
+
return input;
|
|
194
|
+
};
|
|
195
|
+
if (state.platform === "discord") {
|
|
196
|
+
const guild = document.createElement("select"); guild.id = "guild";
|
|
197
|
+
const label = document.createElement("label"); label.textContent = "Server (guild)"; label.htmlFor = "guild";
|
|
198
|
+
host.append(label, guild);
|
|
199
|
+
for (const g of state.guilds) { const opt = document.createElement("option"); opt.value = g.id; opt.textContent = g.name + " (" + g.id + ")"; guild.append(opt); }
|
|
200
|
+
field("general", "General channel id");
|
|
201
|
+
field("admin", "Your Discord user id", "Becomes the first allowed user.");
|
|
202
|
+
} else {
|
|
203
|
+
field("group", "Group id", "Or post any message in the group and press Detect.");
|
|
204
|
+
const row = document.createElement("div"); row.className = "row";
|
|
205
|
+
const detect = document.createElement("button"); detect.textContent = "Detect from a message";
|
|
206
|
+
detect.onclick = detectGroup; row.append(detect);
|
|
207
|
+
const msg = document.createElement("span"); msg.className = "msg"; msg.id = "detectMsg"; row.append(msg);
|
|
208
|
+
host.append(row);
|
|
209
|
+
field("admin", "Your Telegram user id", "Becomes the first allowed user.");
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function detectGroup() {
|
|
214
|
+
$("detectMsg").textContent = "Waiting for a message in the group…";
|
|
215
|
+
const res = await api("api/settings/quickstart/probe", { method: "POST", body: JSON.stringify({ action: "await-telegram-start", token: $("token").value.trim(), offset: state.offset }) });
|
|
216
|
+
if (!res.ok) { $("detectMsg").textContent = res.body?.error || "Failed."; return; }
|
|
217
|
+
state.offset = res.body.offset;
|
|
218
|
+
if (res.body.found) {
|
|
219
|
+
$("group").value = String(res.body.found.groupId);
|
|
220
|
+
$("admin").value = String(res.body.found.userId);
|
|
221
|
+
$("detectMsg").textContent = "Found.";
|
|
222
|
+
} else $("detectMsg").textContent = "No group message yet — post one and press Detect again.";
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function input() {
|
|
226
|
+
return {
|
|
227
|
+
platform: state.platform,
|
|
228
|
+
token_env: $("tokenEnv").value.trim(),
|
|
229
|
+
backend: $("backend").value,
|
|
230
|
+
working_directory: $("dir").value.trim(),
|
|
231
|
+
instance_name: $("name").value.trim(),
|
|
232
|
+
group_id: $("group") ? $("group").value.trim() || undefined : undefined,
|
|
233
|
+
guild_id: $("guild") ? $("guild").value || undefined : undefined,
|
|
234
|
+
general_channel_id: $("general") ? $("general").value.trim() || undefined : undefined,
|
|
235
|
+
admin_user_id: $("admin") ? $("admin").value.trim() || undefined : undefined,
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
$("verify").onclick = async () => {
|
|
240
|
+
$("verifyMsg").textContent = "Asking the provider…";
|
|
241
|
+
const token = $("token").value.trim();
|
|
242
|
+
const res = await api("api/settings/quickstart/probe", { method: "POST", body: JSON.stringify({ action: "verify", platform: state.platform, token }) });
|
|
243
|
+
state.identity = res.body?.identity || { valid: false };
|
|
244
|
+
$("verifyMsg").className = "msg " + (state.identity.valid ? "ok" : "err");
|
|
245
|
+
$("verifyMsg").textContent = state.identity.valid ? "✓ " + (state.identity.username || "verified") : "✗ " + (state.identity.reason || "rejected");
|
|
246
|
+
if (state.identity.valid && state.platform === "discord") {
|
|
247
|
+
const guilds = await api("api/settings/quickstart/probe", { method: "POST", body: JSON.stringify({ action: "guilds", token }) });
|
|
248
|
+
state.guilds = guilds.body?.guilds || [];
|
|
249
|
+
renderPlatformFields();
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
$("preview").onclick = async () => {
|
|
254
|
+
const res = await api("api/settings/quickstart/plan", { method: "POST", body: JSON.stringify(input()) });
|
|
255
|
+
if (!res.ok) { $("planOut").hidden = false; $("planOut").textContent = res.body?.error || "Failed."; return; }
|
|
256
|
+
state.plan = res.body;
|
|
257
|
+
$("planOut").hidden = false;
|
|
258
|
+
$("planOut").textContent = JSON.stringify(res.body, null, 2);
|
|
259
|
+
$("finish").disabled = !state.identity?.valid;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
$("finish").onclick = async () => {
|
|
263
|
+
$("finish").disabled = true;
|
|
264
|
+
$("finishMsg").textContent = "Writing configuration…";
|
|
265
|
+
const commit = await api("api/settings/quickstart/commit", { method: "POST", body: JSON.stringify(Object.assign({}, input(), { token: $("token").value.trim() })) });
|
|
266
|
+
if (!commit.ok) { $("finishMsg").className = "msg err"; $("finishMsg").textContent = commit.body?.error || "Failed."; $("finish").disabled = false; return; }
|
|
267
|
+
const finished = await api("setup/finish", { method: "POST" });
|
|
268
|
+
$("finishMsg").className = "msg";
|
|
269
|
+
if (finished.body && finished.body.watch === false) {
|
|
270
|
+
// Behind a tunnel this page and its hostname are both about to stop
|
|
271
|
+
// existing, so there is nothing here to watch. And the dashboard is not
|
|
272
|
+
// the thing to promise: it binds loopback, so a link to it is one this
|
|
273
|
+
// phone cannot open. The channel is what was just set up, and it is
|
|
274
|
+
// where the agent will be.
|
|
275
|
+
$("finishMsg").className = "msg ok";
|
|
276
|
+
$("finishMsg").textContent = "AgEnD is starting. Talk to it in the channel you just set up — this page is done.";
|
|
277
|
+
return;
|
|
278
|
+
}
|
|
279
|
+
$("finishMsg").textContent = "Starting AgEnD… this page will stop responding while the port changes hands.";
|
|
280
|
+
waitForFleet();
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// The host is gone; the fleet is binding the same port. Connection refused is
|
|
284
|
+
// the expected state in between, so it is not an error until the cap.
|
|
285
|
+
function waitForFleet() {
|
|
286
|
+
const deadline = Date.now() + 60_000;
|
|
287
|
+
const tick = async () => {
|
|
288
|
+
try {
|
|
289
|
+
const res = await fetch("/health", { cache: "no-store" });
|
|
290
|
+
if (res.status) {
|
|
291
|
+
$("finishMsg").className = "msg ok";
|
|
292
|
+
$("finishMsg").textContent = "AgEnD is up. Talk to it in the channel you just set up.";
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
} catch { /* not listening yet */ }
|
|
296
|
+
if (Date.now() > deadline) {
|
|
297
|
+
$("finishMsg").className = "msg err";
|
|
298
|
+
$("finishMsg").textContent = "AgEnD has not come up within 60 seconds. Run \`agend start\` on the machine itself and check fleet.log — this page cannot tell you any more.";
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
setTimeout(tick, 1000);
|
|
302
|
+
};
|
|
303
|
+
setTimeout(tick, 1500);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
$("pickTelegram").onclick = () => setPlatform("telegram");
|
|
307
|
+
$("pickDiscord").onclick = () => setPlatform("discord");
|
|
308
|
+
loadEnvironment();
|
|
309
|
+
setPlatform("telegram");
|
|
310
|
+
})();
|
|
311
|
+
</script>
|
|
312
|
+
</body>
|
|
313
|
+
</html>
|
|
314
|
+
`;
|
|
315
|
+
//# sourceMappingURL=setup-form.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup-form.js","sourceRoot":"","sources":["../src/setup-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8DnC,CAAC;AAEF,MAAM,CAAC,MAAM,eAAe,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA0O9B,CAAC"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { type FleetLockProbe } from "./fleet-lock.js";
|
|
2
|
+
import { SetupCredentials } from "./setup-auth.js";
|
|
3
|
+
import type { TunnelProvider } from "./tunnel/types.js";
|
|
4
|
+
/** How long the form may stay open at all. */
|
|
5
|
+
export declare const SETUP_HOST_TTL_MS: number;
|
|
6
|
+
/**
|
|
7
|
+
* …and how long it may stay open with nobody touching it.
|
|
8
|
+
*
|
|
9
|
+
* Strictly shorter than the TTL, which it was not: both were fifteen minutes,
|
|
10
|
+
* so the idle timer could never fire first and the page always stayed open for
|
|
11
|
+
* the full quarter hour whether anyone was using it or not.
|
|
12
|
+
*/
|
|
13
|
+
export declare const SETUP_HOST_IDLE_MS: number;
|
|
14
|
+
export interface SetupHostOptions {
|
|
15
|
+
dataDir: string;
|
|
16
|
+
configPath: string;
|
|
17
|
+
port: number;
|
|
18
|
+
ttlMs?: number;
|
|
19
|
+
idleMs?: number;
|
|
20
|
+
/** Injected so a test can observe the handover without starting a fleet. */
|
|
21
|
+
spawnFleet?: () => void;
|
|
22
|
+
now?: () => number;
|
|
23
|
+
log?: (message: string) => void;
|
|
24
|
+
/** Test seam: the same probe `acquireFleetLock` already accepts. */
|
|
25
|
+
lockProbe?: FleetLockProbe;
|
|
26
|
+
/** Fixed in tests so the URL and the code are predictable. */
|
|
27
|
+
credentials?: SetupCredentials;
|
|
28
|
+
/**
|
|
29
|
+
* Expose the page through a tunnel.
|
|
30
|
+
*
|
|
31
|
+
* Turning this on forces the listener onto an ephemeral port — see
|
|
32
|
+
* `resolvePort`. That is not a convenience; it is the reason a tunnel that
|
|
33
|
+
* outlives this process cannot end up pointed at a fleet dashboard.
|
|
34
|
+
*/
|
|
35
|
+
tunnel?: boolean;
|
|
36
|
+
/** Test seam: the real one runs cloudflared. */
|
|
37
|
+
tunnelProvider?: TunnelProvider;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The port a setup host may bind.
|
|
41
|
+
*
|
|
42
|
+
* In tunnel mode it is always zero. Not "unless `--port` was given", and not
|
|
43
|
+
* "unless fleet.yaml names a health port" — those are the two ways a fixed
|
|
44
|
+
* port creeps back behind a tunnel, and the second one needs no flag at all.
|
|
45
|
+
* Whatever the fleet would later bind must never be the thing the tunnel is
|
|
46
|
+
* pointed at.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolvePort(opts: {
|
|
49
|
+
tunnel?: boolean;
|
|
50
|
+
port?: number;
|
|
51
|
+
healthPort?: number;
|
|
52
|
+
}): number;
|
|
53
|
+
export declare class SetupHost {
|
|
54
|
+
private readonly opts;
|
|
55
|
+
private server;
|
|
56
|
+
private lock;
|
|
57
|
+
private ttlTimer;
|
|
58
|
+
private idleTimer;
|
|
59
|
+
private stopping;
|
|
60
|
+
private readonly credentials;
|
|
61
|
+
/** Where this page lives. Random, and not a credential — see setup-auth.ts. */
|
|
62
|
+
readonly sid: string;
|
|
63
|
+
/** What the person types. Printed by the CLI; never put in a URL. */
|
|
64
|
+
readonly code: string;
|
|
65
|
+
constructor(opts: SetupHostOptions);
|
|
66
|
+
/** The page's own path. Everything outside it is not this page. */
|
|
67
|
+
private get basePath();
|
|
68
|
+
private managedTunnel;
|
|
69
|
+
private tunnelHandle;
|
|
70
|
+
/** The exact host the tunnel answers on; nothing else is added to the set. */
|
|
71
|
+
private externalHost;
|
|
72
|
+
private tunnelStopResult;
|
|
73
|
+
/**
|
|
74
|
+
* Set when the wizard asked to hand over, so a shutdown that started for
|
|
75
|
+
* another reason still completes it.
|
|
76
|
+
*
|
|
77
|
+
* Without this the tunnel dying in the moment between "finish" answering and
|
|
78
|
+
* the handover beginning would take the `stopping` flag first, and the
|
|
79
|
+
* handover would silently return: configuration written, page saying
|
|
80
|
+
* "starting", and no fleet.
|
|
81
|
+
*/
|
|
82
|
+
private handoverRequested;
|
|
83
|
+
private get ttlMs();
|
|
84
|
+
private get idleMs();
|
|
85
|
+
private log;
|
|
86
|
+
/** The config the wizard edits: a file if there is one, otherwise a blank. */
|
|
87
|
+
private readConfig;
|
|
88
|
+
private context;
|
|
89
|
+
start(): Promise<{
|
|
90
|
+
port: number;
|
|
91
|
+
sid: string;
|
|
92
|
+
code: string;
|
|
93
|
+
path: string;
|
|
94
|
+
publicUrl: string | null;
|
|
95
|
+
}>;
|
|
96
|
+
/**
|
|
97
|
+
* Put the page behind a tunnel, or say why it is not.
|
|
98
|
+
*
|
|
99
|
+
* Three outcomes, and the third is the one that matters: a start that failed
|
|
100
|
+
* while leaving a process it could not account for is not a fallback, it is a
|
|
101
|
+
* stop. Carrying on would mean serving this page on loopback while something
|
|
102
|
+
* we cannot see may still be exposing it.
|
|
103
|
+
*/
|
|
104
|
+
private openTunnel;
|
|
105
|
+
private boundPort;
|
|
106
|
+
/**
|
|
107
|
+
* The hosts this listener will answer to.
|
|
108
|
+
*
|
|
109
|
+
* Origin==Host alone accepts any Host at all, which was harmless while the
|
|
110
|
+
* only way in was loopback. It stops being harmless the moment something in
|
|
111
|
+
* front of the listener can set it, so the set is named rather than inferred.
|
|
112
|
+
*/
|
|
113
|
+
private allowedHosts;
|
|
114
|
+
/**
|
|
115
|
+
* Whether the browser reached this over TLS.
|
|
116
|
+
*
|
|
117
|
+
* Derived from what this process knows it is serving, never from
|
|
118
|
+
* `X-Forwarded-Proto`: that header is set by whatever is in front, including
|
|
119
|
+
* an attacker, and a cookie's Secure attribute is not something to take on
|
|
120
|
+
* trust from a request.
|
|
121
|
+
*/
|
|
122
|
+
private get endpointIsHttps();
|
|
123
|
+
private touch;
|
|
124
|
+
/**
|
|
125
|
+
* The answer for anything that is not this page.
|
|
126
|
+
*
|
|
127
|
+
* Byte-identical whatever the reason, because the difference between "wrong
|
|
128
|
+
* sid" and "no such path" is precisely how a sid gets found.
|
|
129
|
+
*/
|
|
130
|
+
private notFound;
|
|
131
|
+
private deny;
|
|
132
|
+
private hostAllowed;
|
|
133
|
+
private presentedCookie;
|
|
134
|
+
/** Spend an attempt's worth of consequence: at zero the page is over. */
|
|
135
|
+
private afterFailure;
|
|
136
|
+
private handle;
|
|
137
|
+
/** Exchange the typed code for a session cookie. The only way in. */
|
|
138
|
+
private redeem;
|
|
139
|
+
/**
|
|
140
|
+
* Let go of the port, then hand it over.
|
|
141
|
+
*
|
|
142
|
+
* `closeAllConnections()` first, and not as a nicety: a browser polling for
|
|
143
|
+
* the fleet holds a connection open, and `close()` waits for it. Measured, a
|
|
144
|
+
* single open poll stops the close from ever completing — so the form would
|
|
145
|
+
* hang at "starting…" and the fleet would never be spawned.
|
|
146
|
+
*/
|
|
147
|
+
shutdown(spawnSuccessor: boolean, reason: string): Promise<void>;
|
|
148
|
+
/**
|
|
149
|
+
* Whether an unconfirmed tunnel may be left behind while the fleet starts.
|
|
150
|
+
*
|
|
151
|
+
* Ruled on by fable, and conditional by construction. The reasoning only
|
|
152
|
+
* holds because a leftover tunnel points at an ephemeral port this process
|
|
153
|
+
* owned and nothing will deliberately bind again — so the damage of being
|
|
154
|
+
* wrong is a URL that answers "unavailable", not a public dashboard.
|
|
155
|
+
*
|
|
156
|
+
* **If anyone puts this host back on the health port, this ruling is void**
|
|
157
|
+
* and the path must return to refusing the handover. That is the first
|
|
158
|
+
* condition below rather than a note in a comment, precisely so the change
|
|
159
|
+
* that breaks the reasoning also flips the behaviour.
|
|
160
|
+
*/
|
|
161
|
+
private mayHandOverWithUnconfirmedTunnel;
|
|
162
|
+
}
|