@kynver-app/runtime 0.1.5 → 0.1.6
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/README.md +13 -2
- package/dist/cli.js +199 -38
- package/dist/cli.js.map +4 -4
- package/dist/index.js +199 -38
- package/dist/index.js.map +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,18 +8,29 @@ Standalone Kynver AgentOS execution runtime and CLI (`kynver`).
|
|
|
8
8
|
npm run kynver:build
|
|
9
9
|
npm run kynver -- setup --api-base-url http://localhost:3000 --agent-os-id <id> --repo /path/to/repo --max-workers 13
|
|
10
10
|
npm run kynver -- login --api-key "$KYNVER_API_KEY"
|
|
11
|
+
npm run kynver -- runner credential --agent-os-id <id>
|
|
11
12
|
npm run kynver -- run create --repo /path/to/repo --name my-run
|
|
12
13
|
npm run kynver -- daemon --run <runId> --agent-os-id <id> --execute
|
|
13
14
|
```
|
|
14
15
|
|
|
15
|
-
|
|
16
|
+
### Callback auth (scoped runner tokens)
|
|
17
|
+
|
|
18
|
+
User-hosted runners should **not** use deployment-wide `OPENCLAW_CRON_SECRET` / `KYNVER_RUNTIME_SECRET`. After `kynver login`, mint a workspace-bound token:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
kynver runner credential --agent-os-id <id>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The token (`krc1.*`) is stored in `~/.kynver/credentials` as `runnerToken` and sent as `X-Kynver-Runner-Token` on dispatch/sweep/completion/progress callbacks. It is bound to one `agentOsId` and a capability set (`tasks`, `harness`, `plans.progress`, `runtime.read`, `operator`). The server persists only a token hash plus audit metadata, so credentials can be listed, revoked, and rotated without exposing the clear token again.
|
|
25
|
+
|
|
26
|
+
Global env secrets remain supported for first-party dogfood, QStash, and OpenClaw cron — server-side `verifyHarnessCallback` accepts QStash signatures, scoped tokens, or global secrets (in that precedence for non-QStash callers).
|
|
16
27
|
|
|
17
28
|
## CLI verbs
|
|
18
29
|
|
|
19
30
|
```text
|
|
20
31
|
run create | list | status | dispatch | sweep
|
|
21
32
|
worker start | status | tail | stop | complete
|
|
22
|
-
login | setup | daemon
|
|
33
|
+
login | runner credential | setup | daemon
|
|
23
34
|
```
|
|
24
35
|
|
|
25
36
|
`scripts/opus-harness.mjs` is a compatibility shim that delegates to this package.
|
package/dist/cli.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
4
|
import { mkdirSync as mkdirSync5 } from "node:fs";
|
|
5
|
-
import
|
|
5
|
+
import path14 from "node:path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
|
|
8
8
|
// src/config.ts
|
|
@@ -132,20 +132,112 @@ function saveUserConfig(config) {
|
|
|
132
132
|
writeFileSync2(CONFIG_FILE, `${JSON.stringify(config, null, 2)}
|
|
133
133
|
`, { mode: 384 });
|
|
134
134
|
}
|
|
135
|
-
function
|
|
135
|
+
function loadCredentialsFile() {
|
|
136
|
+
if (!existsSync2(CREDENTIALS_FILE)) return {};
|
|
137
|
+
try {
|
|
138
|
+
return JSON.parse(readFileSync2(CREDENTIALS_FILE, "utf8"));
|
|
139
|
+
} catch {
|
|
140
|
+
return {};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
function saveCredentialsFile(parsed) {
|
|
136
144
|
mkdirSync2(CONFIG_DIR, { recursive: true });
|
|
137
|
-
writeFileSync2(CREDENTIALS_FILE, `${JSON.stringify(
|
|
145
|
+
writeFileSync2(CREDENTIALS_FILE, `${JSON.stringify(parsed, null, 2)}
|
|
138
146
|
`, { mode: 384 });
|
|
139
147
|
}
|
|
148
|
+
function loadApiKey() {
|
|
149
|
+
if (process.env.KYNVER_API_KEY) return process.env.KYNVER_API_KEY;
|
|
150
|
+
return loadCredentialsFile().apiKey;
|
|
151
|
+
}
|
|
152
|
+
function saveApiKey(apiKey) {
|
|
153
|
+
saveCredentialsFile({ ...loadCredentialsFile(), apiKey });
|
|
154
|
+
}
|
|
155
|
+
function loadRunnerToken(agentOsId) {
|
|
156
|
+
const creds = loadCredentialsFile();
|
|
157
|
+
if (!creds.runnerToken) return void 0;
|
|
158
|
+
if (agentOsId && creds.runnerTokenAgentOsId && creds.runnerTokenAgentOsId !== agentOsId) {
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
return creds.runnerToken;
|
|
162
|
+
}
|
|
163
|
+
function saveRunnerToken(agentOsId, token) {
|
|
164
|
+
saveCredentialsFile({
|
|
165
|
+
...loadCredentialsFile(),
|
|
166
|
+
runnerToken: token,
|
|
167
|
+
runnerTokenAgentOsId: agentOsId
|
|
168
|
+
});
|
|
169
|
+
}
|
|
140
170
|
function resolveBaseUrl(argsBaseUrl) {
|
|
141
171
|
const baseUrl = argsBaseUrl || process.env.KYNVER_API_URL || process.env.OPENCLAW_CRON_FIRE_BASE_URL || loadUserConfig().apiBaseUrl;
|
|
142
172
|
if (!baseUrl) failConfig("requires --base-url, KYNVER_API_URL, OPENCLAW_CRON_FIRE_BASE_URL, or ~/.kynver/config.json apiBaseUrl");
|
|
143
173
|
return trimTrailingSlash(String(baseUrl));
|
|
144
174
|
}
|
|
145
|
-
function resolveCallbackSecret(argsSecret) {
|
|
146
|
-
const
|
|
147
|
-
if (
|
|
148
|
-
|
|
175
|
+
function resolveCallbackSecret(argsSecret, agentOsId) {
|
|
176
|
+
const scoped = argsSecret || loadRunnerToken(agentOsId) || loadRunnerToken(loadUserConfig().agentOsId);
|
|
177
|
+
if (scoped) return String(scoped);
|
|
178
|
+
const globalSecret = process.env.KYNVER_RUNTIME_SECRET || process.env.OPENCLAW_CRON_SECRET;
|
|
179
|
+
if (globalSecret) {
|
|
180
|
+
console.warn(
|
|
181
|
+
"[kynver] using deployment-level callback secret; run `kynver runner credential --agent-os-id <id>` for a scoped token"
|
|
182
|
+
);
|
|
183
|
+
return String(globalSecret);
|
|
184
|
+
}
|
|
185
|
+
failConfig(
|
|
186
|
+
"requires --secret, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, or (legacy) KYNVER_RUNTIME_SECRET / OPENCLAW_CRON_SECRET"
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
async function fetchRunnerCredential(agentOsId, opts) {
|
|
190
|
+
const apiKey = opts?.apiKey || loadApiKey();
|
|
191
|
+
if (!apiKey) throw new Error("API key required \u2014 run `kynver login` first");
|
|
192
|
+
const base = resolveBaseUrl(opts?.baseUrl);
|
|
193
|
+
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/runner-credentials`;
|
|
194
|
+
const res = await fetch(url, {
|
|
195
|
+
method: "POST",
|
|
196
|
+
headers: {
|
|
197
|
+
"Content-Type": "application/json",
|
|
198
|
+
Authorization: `Bearer ${apiKey}`
|
|
199
|
+
},
|
|
200
|
+
body: JSON.stringify({})
|
|
201
|
+
});
|
|
202
|
+
const text = await res.text();
|
|
203
|
+
let parsed = null;
|
|
204
|
+
try {
|
|
205
|
+
parsed = JSON.parse(text);
|
|
206
|
+
} catch {
|
|
207
|
+
parsed = null;
|
|
208
|
+
}
|
|
209
|
+
if (!res.ok || !parsed?.token) {
|
|
210
|
+
throw new Error(
|
|
211
|
+
`runner credential mint failed (${res.status}): ${parsed?.error ?? text.slice(0, 200)}`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return parsed.token;
|
|
215
|
+
}
|
|
216
|
+
async function mintRunnerCredential(args) {
|
|
217
|
+
const agentOsId = (args.agentOsId ? String(args.agentOsId) : loadUserConfig().agentOsId) || "";
|
|
218
|
+
if (!agentOsId) failConfig("runner credential requires --agent-os-id or agentOsId in ~/.kynver/config.json");
|
|
219
|
+
try {
|
|
220
|
+
const token = await fetchRunnerCredential(agentOsId, {
|
|
221
|
+
baseUrl: args.baseUrl ? String(args.baseUrl) : void 0
|
|
222
|
+
});
|
|
223
|
+
saveRunnerToken(agentOsId, token);
|
|
224
|
+
console.log(
|
|
225
|
+
JSON.stringify(
|
|
226
|
+
{
|
|
227
|
+
ok: true,
|
|
228
|
+
agentOsId,
|
|
229
|
+
credentialsPath: CREDENTIALS_FILE,
|
|
230
|
+
tokenPrefix: `${token.slice(0, 12)}\u2026`,
|
|
231
|
+
note: "Scoped runner token saved; callbacks use X-Kynver-Runner-Token."
|
|
232
|
+
},
|
|
233
|
+
null,
|
|
234
|
+
2
|
|
235
|
+
)
|
|
236
|
+
);
|
|
237
|
+
} catch (err) {
|
|
238
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
239
|
+
process.exit(1);
|
|
240
|
+
}
|
|
149
241
|
}
|
|
150
242
|
function failConfig(message) {
|
|
151
243
|
console.error(message);
|
|
@@ -180,13 +272,28 @@ async function runSetup(args) {
|
|
|
180
272
|
workerProvider: typeof args.provider === "string" ? args.provider : existing.workerProvider || "claude"
|
|
181
273
|
};
|
|
182
274
|
saveUserConfig(config);
|
|
275
|
+
let runnerCredentialNote;
|
|
276
|
+
const apiKey = loadApiKey();
|
|
277
|
+
const agentOsId = config.agentOsId;
|
|
278
|
+
if (apiKey && agentOsId) {
|
|
279
|
+
try {
|
|
280
|
+
const token = await fetchRunnerCredential(agentOsId, {
|
|
281
|
+
baseUrl: typeof args.apiBaseUrl === "string" ? args.apiBaseUrl : config.apiBaseUrl,
|
|
282
|
+
apiKey
|
|
283
|
+
});
|
|
284
|
+
saveRunnerToken(agentOsId, token);
|
|
285
|
+
runnerCredentialNote = "Scoped runner token minted and saved to ~/.kynver/credentials.";
|
|
286
|
+
} catch {
|
|
287
|
+
runnerCredentialNote = "Runner token not minted (server offline or master secret unset). Run `kynver runner credential` after deploy.";
|
|
288
|
+
}
|
|
289
|
+
}
|
|
183
290
|
console.log(
|
|
184
291
|
JSON.stringify(
|
|
185
292
|
{
|
|
186
293
|
ok: true,
|
|
187
294
|
configPath: CONFIG_FILE,
|
|
188
295
|
config,
|
|
189
|
-
note: "Set worker limit once with --max-workers N (or omit to auto-size from RAM).
|
|
296
|
+
note: runnerCredentialNote ?? "Set worker limit once with --max-workers N (or omit to auto-size from RAM). Run `kynver login` + `kynver runner credential` for scoped callbacks."
|
|
190
297
|
},
|
|
191
298
|
null,
|
|
192
299
|
2
|
|
@@ -200,15 +307,27 @@ async function runLogin(args) {
|
|
|
200
307
|
console.log(JSON.stringify({ ok: true, credentialsPath: CREDENTIALS_FILE }, null, 2));
|
|
201
308
|
}
|
|
202
309
|
|
|
310
|
+
// src/callback-headers.ts
|
|
311
|
+
function buildHarnessCallbackHeaders(secret) {
|
|
312
|
+
const trimmed = String(secret).trim();
|
|
313
|
+
if (trimmed.startsWith("krc1.")) {
|
|
314
|
+
return {
|
|
315
|
+
"Content-Type": "application/json",
|
|
316
|
+
"X-Kynver-Runner-Token": trimmed
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
return {
|
|
320
|
+
"Content-Type": "application/json",
|
|
321
|
+
"X-OpenClaw-Cron-Secret": trimmed,
|
|
322
|
+
"X-Kynver-Runtime-Secret": trimmed
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
203
326
|
// src/callbacks.ts
|
|
204
327
|
async function postJson(url, secret, body) {
|
|
205
328
|
const res = await fetch(url, {
|
|
206
329
|
method: "POST",
|
|
207
|
-
headers:
|
|
208
|
-
"Content-Type": "application/json",
|
|
209
|
-
"X-OpenClaw-Cron-Secret": String(secret),
|
|
210
|
-
"X-Kynver-Runtime-Secret": String(secret)
|
|
211
|
-
},
|
|
330
|
+
headers: buildHarnessCallbackHeaders(secret),
|
|
212
331
|
body: JSON.stringify(body)
|
|
213
332
|
});
|
|
214
333
|
let response = null;
|
|
@@ -222,10 +341,7 @@ async function postJson(url, secret, body) {
|
|
|
222
341
|
async function getJson(url, secret) {
|
|
223
342
|
const res = await fetch(url, {
|
|
224
343
|
method: "GET",
|
|
225
|
-
headers:
|
|
226
|
-
"X-OpenClaw-Cron-Secret": String(secret),
|
|
227
|
-
"X-Kynver-Runtime-Secret": String(secret)
|
|
228
|
-
}
|
|
344
|
+
headers: buildHarnessCallbackHeaders(secret)
|
|
229
345
|
});
|
|
230
346
|
let response = null;
|
|
231
347
|
try {
|
|
@@ -243,12 +359,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
|
|
|
243
359
|
var DEFAULT_MAX_USED_PERCENT = 80;
|
|
244
360
|
var DEFAULT_HARD_MAX_USED_PERCENT = 90;
|
|
245
361
|
function observeRunnerDiskGate(input = {}) {
|
|
246
|
-
const
|
|
362
|
+
const path15 = input.diskPath?.trim() || "/";
|
|
247
363
|
const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
|
|
248
364
|
const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
|
|
249
365
|
const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
|
|
250
366
|
const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
|
|
251
|
-
const stats = statfsSync(
|
|
367
|
+
const stats = statfsSync(path15);
|
|
252
368
|
const freeBytes = Number(stats.bavail) * Number(stats.bsize);
|
|
253
369
|
const totalBytes = Number(stats.blocks) * Number(stats.bsize);
|
|
254
370
|
const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
|
|
@@ -268,7 +384,7 @@ function observeRunnerDiskGate(input = {}) {
|
|
|
268
384
|
}
|
|
269
385
|
return {
|
|
270
386
|
ok,
|
|
271
|
-
path:
|
|
387
|
+
path: path15,
|
|
272
388
|
freeBytes,
|
|
273
389
|
totalBytes,
|
|
274
390
|
usedPercent,
|
|
@@ -874,6 +990,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
874
990
|
ownedPaths: opts.ownedPaths,
|
|
875
991
|
...opts.agentOsId ? { agentOsId: String(opts.agentOsId) } : {},
|
|
876
992
|
...opts.taskId ? { taskId: String(opts.taskId) } : {},
|
|
993
|
+
...opts.planId ? { planId: String(opts.planId) } : {},
|
|
877
994
|
...opts.leaseOwner ? { leaseOwner: String(opts.leaseOwner) } : {},
|
|
878
995
|
...opts.dispatched ? { dispatched: true } : {},
|
|
879
996
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -940,7 +1057,7 @@ async function dispatchRun(args) {
|
|
|
940
1057
|
const run = loadRun(String(required(String(args.run || ""), "--run")));
|
|
941
1058
|
const agentOsId = String(required(String(args.agentOsId || ""), "--agent-os-id"));
|
|
942
1059
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
943
|
-
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0);
|
|
1060
|
+
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0, agentOsId);
|
|
944
1061
|
const execute = args.execute === true || args.execute === "true";
|
|
945
1062
|
const dryRun = !execute;
|
|
946
1063
|
const leaseOwner = `openclaw-harness:${run.id}`;
|
|
@@ -1014,6 +1131,7 @@ async function dispatchRun(args) {
|
|
|
1014
1131
|
const task = decision.task;
|
|
1015
1132
|
const name = safeSlug(`t-${task.id}-a${task.attempt}`);
|
|
1016
1133
|
try {
|
|
1134
|
+
const planId = task.planId ? String(task.planId) : void 0;
|
|
1017
1135
|
const worker = spawnWorkerProcess(run, {
|
|
1018
1136
|
name,
|
|
1019
1137
|
task: buildDispatchTaskText(task, agentOsId),
|
|
@@ -1021,6 +1139,7 @@ async function dispatchRun(args) {
|
|
|
1021
1139
|
model: args.model ? String(args.model) : void 0,
|
|
1022
1140
|
agentOsId,
|
|
1023
1141
|
taskId: String(task.id),
|
|
1142
|
+
planId,
|
|
1024
1143
|
leaseOwner,
|
|
1025
1144
|
dispatched: true
|
|
1026
1145
|
});
|
|
@@ -1082,7 +1201,7 @@ async function sweepRun(args) {
|
|
|
1082
1201
|
const run = loadRun(String(required(String(args.run || ""), "--run")));
|
|
1083
1202
|
const agentOsId = String(required(String(args.agentOsId || ""), "--agent-os-id"));
|
|
1084
1203
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
1085
|
-
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0);
|
|
1204
|
+
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0, agentOsId);
|
|
1086
1205
|
const leaseOwner = `openclaw-harness:${run.id}`;
|
|
1087
1206
|
const releasedLocalOrphans = [];
|
|
1088
1207
|
for (const name of Object.keys(run.workers || {})) {
|
|
@@ -1200,7 +1319,7 @@ async function tryCompleteWorker(args) {
|
|
|
1200
1319
|
return { ok: true, skipped: true, reason: "worker-not-finished" };
|
|
1201
1320
|
}
|
|
1202
1321
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
1203
|
-
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0);
|
|
1322
|
+
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0, agentOsId);
|
|
1204
1323
|
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/harness/completion`;
|
|
1205
1324
|
const body = {
|
|
1206
1325
|
source: "openclaw-harness",
|
|
@@ -1214,11 +1333,7 @@ async function tryCompleteWorker(args) {
|
|
|
1214
1333
|
};
|
|
1215
1334
|
const res = await fetch(url, {
|
|
1216
1335
|
method: "POST",
|
|
1217
|
-
headers:
|
|
1218
|
-
"Content-Type": "application/json",
|
|
1219
|
-
"X-OpenClaw-Cron-Secret": secret,
|
|
1220
|
-
"X-Kynver-Runtime-Secret": secret
|
|
1221
|
-
},
|
|
1336
|
+
headers: buildHarnessCallbackHeaders(secret),
|
|
1222
1337
|
body: JSON.stringify(body)
|
|
1223
1338
|
});
|
|
1224
1339
|
let parsed = null;
|
|
@@ -1353,12 +1468,57 @@ function stopWorker(args) {
|
|
|
1353
1468
|
}
|
|
1354
1469
|
|
|
1355
1470
|
// src/pipeline-tick.ts
|
|
1471
|
+
import path13 from "node:path";
|
|
1472
|
+
|
|
1473
|
+
// src/plan-progress-daemon-sync.ts
|
|
1356
1474
|
import path12 from "node:path";
|
|
1357
1475
|
|
|
1476
|
+
// src/plan-progress-sync.ts
|
|
1477
|
+
async function syncPlanProgress(args) {
|
|
1478
|
+
const base = resolveBaseUrl(args.baseUrl);
|
|
1479
|
+
const secret = resolveCallbackSecret(args.secret);
|
|
1480
|
+
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(args.agentOsId)}/tasks/${encodeURIComponent(args.taskId)}/plan-progress-sync`;
|
|
1481
|
+
const res = await postJson(url, secret, {
|
|
1482
|
+
phase: args.phase,
|
|
1483
|
+
taskId: args.taskId,
|
|
1484
|
+
blocker: args.blocker,
|
|
1485
|
+
artifact: args.artifact
|
|
1486
|
+
});
|
|
1487
|
+
return { ok: res.ok, status: res.status, response: res.response };
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
// src/plan-progress-daemon-sync.ts
|
|
1491
|
+
async function syncActiveWorkerPlanProgress(runId, args) {
|
|
1492
|
+
const run = loadRun(runId);
|
|
1493
|
+
const agentOsId = String(args.agentOsId || "");
|
|
1494
|
+
if (!agentOsId) return [];
|
|
1495
|
+
const outcomes = [];
|
|
1496
|
+
for (const name of Object.keys(run.workers || {})) {
|
|
1497
|
+
const worker = readJson(
|
|
1498
|
+
path12.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1499
|
+
null
|
|
1500
|
+
);
|
|
1501
|
+
if (!worker?.dispatched || !worker.taskId) continue;
|
|
1502
|
+
const status = computeWorkerStatus(worker);
|
|
1503
|
+
if (status.heartbeatBlocker) {
|
|
1504
|
+
const res = await syncPlanProgress({
|
|
1505
|
+
agentOsId,
|
|
1506
|
+
taskId: worker.taskId,
|
|
1507
|
+
phase: "heartbeat_blocker",
|
|
1508
|
+
blocker: status.heartbeatBlocker,
|
|
1509
|
+
baseUrl: args.baseUrl ? String(args.baseUrl) : void 0,
|
|
1510
|
+
secret: args.secret ? String(args.secret) : void 0
|
|
1511
|
+
});
|
|
1512
|
+
outcomes.push({ worker: name, phase: "heartbeat_blocker", ok: res.ok });
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
return outcomes;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1358
1518
|
// src/workspace-runtime-config.ts
|
|
1359
1519
|
async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
1360
1520
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
1361
|
-
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0);
|
|
1521
|
+
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0, agentOsId);
|
|
1362
1522
|
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/runtime`;
|
|
1363
1523
|
try {
|
|
1364
1524
|
const res = await getJson(url, secret);
|
|
@@ -1382,7 +1542,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
1382
1542
|
const outcomes = [];
|
|
1383
1543
|
for (const name of Object.keys(run.workers || {})) {
|
|
1384
1544
|
const worker = readJson(
|
|
1385
|
-
|
|
1545
|
+
path13.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1386
1546
|
null
|
|
1387
1547
|
);
|
|
1388
1548
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -1401,7 +1561,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
1401
1561
|
}
|
|
1402
1562
|
async function postOperatorTick(agentOsId, runId, resourceGate, args) {
|
|
1403
1563
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
1404
|
-
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0);
|
|
1564
|
+
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0, agentOsId);
|
|
1405
1565
|
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/operator/tick`;
|
|
1406
1566
|
const res = await postJson(url, secret, {
|
|
1407
1567
|
agentOsId,
|
|
@@ -1417,6 +1577,7 @@ async function runPipelineTick(args) {
|
|
|
1417
1577
|
const execute = args.execute !== false && args.execute !== "false";
|
|
1418
1578
|
runStatus({ run: runId });
|
|
1419
1579
|
const completedWorkers = await completeFinishedWorkers(runId, args);
|
|
1580
|
+
const planProgressSync = await syncActiveWorkerPlanProgress(runId, args);
|
|
1420
1581
|
const workspacePrefs = await fetchWorkspaceRuntimePreferences(agentOsId, args);
|
|
1421
1582
|
const resourceGate = observeRunnerResourceGate({
|
|
1422
1583
|
runId,
|
|
@@ -1456,6 +1617,7 @@ async function runPipelineTick(args) {
|
|
|
1456
1617
|
execute,
|
|
1457
1618
|
resourceGate,
|
|
1458
1619
|
completedWorkers,
|
|
1620
|
+
planProgressSync,
|
|
1459
1621
|
operatorTick,
|
|
1460
1622
|
sweep,
|
|
1461
1623
|
dispatch,
|
|
@@ -1523,7 +1685,7 @@ async function emitPlanProgress(args) {
|
|
|
1523
1685
|
evidence.push(parseEvidenceArg(rawEvidence));
|
|
1524
1686
|
}
|
|
1525
1687
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
1526
|
-
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0);
|
|
1688
|
+
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0, agentOsId);
|
|
1527
1689
|
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/plans/${encodeURIComponent(planId)}/progress-events`;
|
|
1528
1690
|
const cfg = loadUserConfig();
|
|
1529
1691
|
const provider = cfg.workerProvider ? `provider:${cfg.workerProvider}` : void 0;
|
|
@@ -1542,10 +1704,7 @@ async function emitPlanProgress(args) {
|
|
|
1542
1704
|
};
|
|
1543
1705
|
const res = await fetch(url, {
|
|
1544
1706
|
method: "POST",
|
|
1545
|
-
headers:
|
|
1546
|
-
"Content-Type": "application/json",
|
|
1547
|
-
"X-OpenClaw-Cron-Secret": secret
|
|
1548
|
-
},
|
|
1707
|
+
headers: buildHarnessCallbackHeaders(secret),
|
|
1549
1708
|
body: JSON.stringify(body)
|
|
1550
1709
|
});
|
|
1551
1710
|
const text = await res.text();
|
|
@@ -1611,6 +1770,7 @@ function usage(code = 0) {
|
|
|
1611
1770
|
[
|
|
1612
1771
|
"Usage:",
|
|
1613
1772
|
" kynver login --api-key KEY",
|
|
1773
|
+
" kynver runner credential [--agent-os-id ID] [--base-url URL]",
|
|
1614
1774
|
" kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--max-workers N] [--provider claude|cursor]",
|
|
1615
1775
|
" kynver daemon --run RUN_ID --agent-os-id AOS_ID [--execute] [--interval-ms MS]",
|
|
1616
1776
|
" kynver run create --repo /path/repo [--name name] [--base origin/main]",
|
|
@@ -1634,7 +1794,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1634
1794
|
const scope = argv.shift();
|
|
1635
1795
|
let action;
|
|
1636
1796
|
let rest;
|
|
1637
|
-
if (scope === "run" || scope === "worker" || scope === "plan") {
|
|
1797
|
+
if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner") {
|
|
1638
1798
|
action = argv.shift();
|
|
1639
1799
|
rest = argv;
|
|
1640
1800
|
} else {
|
|
@@ -1645,6 +1805,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1645
1805
|
mkdirSync5(runsDir, { recursive: true });
|
|
1646
1806
|
mkdirSync5(worktreesDir, { recursive: true });
|
|
1647
1807
|
if (scope === "login") return void await runLogin(args);
|
|
1808
|
+
if (scope === "runner" && action === "credential") return void await mintRunnerCredential(args);
|
|
1648
1809
|
if (scope === "setup") return void await runSetup(args);
|
|
1649
1810
|
if (scope === "daemon") return void await runDaemon(args);
|
|
1650
1811
|
if (scope === "plan" && action === "progress") return void await emitPlanProgress(args);
|
|
@@ -1661,7 +1822,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1661
1822
|
if (scope === "worker" && action === "complete") return void await completeWorker(args);
|
|
1662
1823
|
unknownCommand(scope, action);
|
|
1663
1824
|
}
|
|
1664
|
-
var isCliEntry = process.argv[1] &&
|
|
1825
|
+
var isCliEntry = process.argv[1] && path14.resolve(process.argv[1]) === path14.resolve(fileURLToPath(import.meta.url));
|
|
1665
1826
|
if (isCliEntry) {
|
|
1666
1827
|
void main().catch((error) => {
|
|
1667
1828
|
console.error(error);
|