@kynver-app/runtime 0.1.4 → 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 +218 -44
- package/dist/cli.js.map +4 -4
- package/dist/index.js +218 -44
- 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,
|
|
@@ -645,6 +761,16 @@ import path7 from "node:path";
|
|
|
645
761
|
// src/prompt.ts
|
|
646
762
|
function buildPrompt(input) {
|
|
647
763
|
const ownership = input.ownedPaths.length ? `Owned paths: ${input.ownedPaths.join(", ")}. Do not edit outside these paths without stopping and reporting why.` : "Owned paths: unrestricted for this worker, but keep edits tightly scoped.";
|
|
764
|
+
const progressLines = [
|
|
765
|
+
"Structured plan progress (required when planId is set):",
|
|
766
|
+
"- Harness checkpoints only: `kynver plan progress --plan <planId> --row <rowKey> --role implementer --status running|partial|blocked` (the by-id harness route rejects `done` and confirm events).",
|
|
767
|
+
"- When a slice is finished, emit `partial` with evidence (`--evidence pr:<url>`, `--evidence path:<file>`, or `--evidence command:<cmd>`). Do not propose or confirm row `done` from the worker CLI.",
|
|
768
|
+
"- Propose/confirm row `done` is MCP/session only: chat agents use `agent_os_plan_progress_event_append` on the slug route (implementer proposes with `proposed: true`; report_reviewer/deep_reviewer confirm with `proposed: false`).",
|
|
769
|
+
"- When blocked on operator/Ghost/runtime review, create a linked review task (MCP `agent_os_plan_review_task_create` or API) and pass `--review-task <taskId>`.",
|
|
770
|
+
"- Before the completion report: mark completion-report rows partial with evidence; do not skip report review.",
|
|
771
|
+
"- After implementation: wait for report_reviewer then deep_reviewer confirmation (via MCP/session agents) before follow-up rows close.",
|
|
772
|
+
input.planId ? `Active planId: ${input.planId}${input.taskId ? ` \xB7 taskId: ${input.taskId}` : ""}` : "No planId on this worker \u2014 still emit progress when you touch plan-scoped work."
|
|
773
|
+
];
|
|
648
774
|
return [
|
|
649
775
|
"You are running under the Kynver AgentOS runtime.",
|
|
650
776
|
"Immediately state your plan before editing.",
|
|
@@ -654,6 +780,8 @@ function buildPrompt(input) {
|
|
|
654
780
|
"After each major step, append one JSON line to the heartbeat file with fields: ts, phase, summary, changedFiles, blocker.",
|
|
655
781
|
"Final response must include files changed, verification commands, and unresolved risks.",
|
|
656
782
|
"",
|
|
783
|
+
...progressLines,
|
|
784
|
+
"",
|
|
657
785
|
"Task:",
|
|
658
786
|
input.task
|
|
659
787
|
].join("\n");
|
|
@@ -862,6 +990,7 @@ function spawnWorkerProcess(run, opts) {
|
|
|
862
990
|
ownedPaths: opts.ownedPaths,
|
|
863
991
|
...opts.agentOsId ? { agentOsId: String(opts.agentOsId) } : {},
|
|
864
992
|
...opts.taskId ? { taskId: String(opts.taskId) } : {},
|
|
993
|
+
...opts.planId ? { planId: String(opts.planId) } : {},
|
|
865
994
|
...opts.leaseOwner ? { leaseOwner: String(opts.leaseOwner) } : {},
|
|
866
995
|
...opts.dispatched ? { dispatched: true } : {},
|
|
867
996
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
@@ -928,7 +1057,7 @@ async function dispatchRun(args) {
|
|
|
928
1057
|
const run = loadRun(String(required(String(args.run || ""), "--run")));
|
|
929
1058
|
const agentOsId = String(required(String(args.agentOsId || ""), "--agent-os-id"));
|
|
930
1059
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
931
|
-
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0);
|
|
1060
|
+
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0, agentOsId);
|
|
932
1061
|
const execute = args.execute === true || args.execute === "true";
|
|
933
1062
|
const dryRun = !execute;
|
|
934
1063
|
const leaseOwner = `openclaw-harness:${run.id}`;
|
|
@@ -1002,6 +1131,7 @@ async function dispatchRun(args) {
|
|
|
1002
1131
|
const task = decision.task;
|
|
1003
1132
|
const name = safeSlug(`t-${task.id}-a${task.attempt}`);
|
|
1004
1133
|
try {
|
|
1134
|
+
const planId = task.planId ? String(task.planId) : void 0;
|
|
1005
1135
|
const worker = spawnWorkerProcess(run, {
|
|
1006
1136
|
name,
|
|
1007
1137
|
task: buildDispatchTaskText(task, agentOsId),
|
|
@@ -1009,6 +1139,7 @@ async function dispatchRun(args) {
|
|
|
1009
1139
|
model: args.model ? String(args.model) : void 0,
|
|
1010
1140
|
agentOsId,
|
|
1011
1141
|
taskId: String(task.id),
|
|
1142
|
+
planId,
|
|
1012
1143
|
leaseOwner,
|
|
1013
1144
|
dispatched: true
|
|
1014
1145
|
});
|
|
@@ -1070,7 +1201,7 @@ async function sweepRun(args) {
|
|
|
1070
1201
|
const run = loadRun(String(required(String(args.run || ""), "--run")));
|
|
1071
1202
|
const agentOsId = String(required(String(args.agentOsId || ""), "--agent-os-id"));
|
|
1072
1203
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
1073
|
-
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0);
|
|
1204
|
+
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0, agentOsId);
|
|
1074
1205
|
const leaseOwner = `openclaw-harness:${run.id}`;
|
|
1075
1206
|
const releasedLocalOrphans = [];
|
|
1076
1207
|
for (const name of Object.keys(run.workers || {})) {
|
|
@@ -1188,7 +1319,7 @@ async function tryCompleteWorker(args) {
|
|
|
1188
1319
|
return { ok: true, skipped: true, reason: "worker-not-finished" };
|
|
1189
1320
|
}
|
|
1190
1321
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
1191
|
-
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0);
|
|
1322
|
+
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0, agentOsId);
|
|
1192
1323
|
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/harness/completion`;
|
|
1193
1324
|
const body = {
|
|
1194
1325
|
source: "openclaw-harness",
|
|
@@ -1202,11 +1333,7 @@ async function tryCompleteWorker(args) {
|
|
|
1202
1333
|
};
|
|
1203
1334
|
const res = await fetch(url, {
|
|
1204
1335
|
method: "POST",
|
|
1205
|
-
headers:
|
|
1206
|
-
"Content-Type": "application/json",
|
|
1207
|
-
"X-OpenClaw-Cron-Secret": secret,
|
|
1208
|
-
"X-Kynver-Runtime-Secret": secret
|
|
1209
|
-
},
|
|
1336
|
+
headers: buildHarnessCallbackHeaders(secret),
|
|
1210
1337
|
body: JSON.stringify(body)
|
|
1211
1338
|
});
|
|
1212
1339
|
let parsed = null;
|
|
@@ -1341,12 +1468,57 @@ function stopWorker(args) {
|
|
|
1341
1468
|
}
|
|
1342
1469
|
|
|
1343
1470
|
// src/pipeline-tick.ts
|
|
1471
|
+
import path13 from "node:path";
|
|
1472
|
+
|
|
1473
|
+
// src/plan-progress-daemon-sync.ts
|
|
1344
1474
|
import path12 from "node:path";
|
|
1345
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
|
+
|
|
1346
1518
|
// src/workspace-runtime-config.ts
|
|
1347
1519
|
async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
|
|
1348
1520
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
1349
|
-
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0);
|
|
1521
|
+
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0, agentOsId);
|
|
1350
1522
|
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/runtime`;
|
|
1351
1523
|
try {
|
|
1352
1524
|
const res = await getJson(url, secret);
|
|
@@ -1370,7 +1542,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
1370
1542
|
const outcomes = [];
|
|
1371
1543
|
for (const name of Object.keys(run.workers || {})) {
|
|
1372
1544
|
const worker = readJson(
|
|
1373
|
-
|
|
1545
|
+
path13.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
|
|
1374
1546
|
null
|
|
1375
1547
|
);
|
|
1376
1548
|
if (!worker?.dispatched || !worker.taskId) continue;
|
|
@@ -1389,7 +1561,7 @@ async function completeFinishedWorkers(runId, args) {
|
|
|
1389
1561
|
}
|
|
1390
1562
|
async function postOperatorTick(agentOsId, runId, resourceGate, args) {
|
|
1391
1563
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
1392
|
-
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0);
|
|
1564
|
+
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0, agentOsId);
|
|
1393
1565
|
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/operator/tick`;
|
|
1394
1566
|
const res = await postJson(url, secret, {
|
|
1395
1567
|
agentOsId,
|
|
@@ -1405,6 +1577,7 @@ async function runPipelineTick(args) {
|
|
|
1405
1577
|
const execute = args.execute !== false && args.execute !== "false";
|
|
1406
1578
|
runStatus({ run: runId });
|
|
1407
1579
|
const completedWorkers = await completeFinishedWorkers(runId, args);
|
|
1580
|
+
const planProgressSync = await syncActiveWorkerPlanProgress(runId, args);
|
|
1408
1581
|
const workspacePrefs = await fetchWorkspaceRuntimePreferences(agentOsId, args);
|
|
1409
1582
|
const resourceGate = observeRunnerResourceGate({
|
|
1410
1583
|
runId,
|
|
@@ -1444,6 +1617,7 @@ async function runPipelineTick(args) {
|
|
|
1444
1617
|
execute,
|
|
1445
1618
|
resourceGate,
|
|
1446
1619
|
completedWorkers,
|
|
1620
|
+
planProgressSync,
|
|
1447
1621
|
operatorTick,
|
|
1448
1622
|
sweep,
|
|
1449
1623
|
dispatch,
|
|
@@ -1495,14 +1669,14 @@ function parseEvidenceArg(raw) {
|
|
|
1495
1669
|
return { type: raw.slice(0, idx), value: raw.slice(idx + 1) };
|
|
1496
1670
|
}
|
|
1497
1671
|
async function emitPlanProgress(args) {
|
|
1498
|
-
const planId = required(args, "plan");
|
|
1672
|
+
const planId = required(args.plan ? String(args.plan) : void 0, "plan");
|
|
1499
1673
|
const agentOsId = (args.agentOsId ? String(args.agentOsId) : loadUserConfig().agentOsId) || "";
|
|
1500
1674
|
if (!agentOsId) {
|
|
1501
1675
|
console.error("requires --agent-os-id or agentOsId in ~/.kynver/config.json");
|
|
1502
1676
|
process.exit(1);
|
|
1503
1677
|
}
|
|
1504
|
-
const roleLane = required(args, "role");
|
|
1505
|
-
const status = required(args, "status");
|
|
1678
|
+
const roleLane = required(args.role ? String(args.role) : void 0, "role");
|
|
1679
|
+
const status = required(args.status ? String(args.status) : void 0, "status");
|
|
1506
1680
|
const evidence = [];
|
|
1507
1681
|
const rawEvidence = args.evidence;
|
|
1508
1682
|
if (Array.isArray(rawEvidence)) {
|
|
@@ -1511,8 +1685,10 @@ async function emitPlanProgress(args) {
|
|
|
1511
1685
|
evidence.push(parseEvidenceArg(rawEvidence));
|
|
1512
1686
|
}
|
|
1513
1687
|
const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
|
|
1514
|
-
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0);
|
|
1688
|
+
const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0, agentOsId);
|
|
1515
1689
|
const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/plans/${encodeURIComponent(planId)}/progress-events`;
|
|
1690
|
+
const cfg = loadUserConfig();
|
|
1691
|
+
const provider = cfg.workerProvider ? `provider:${cfg.workerProvider}` : void 0;
|
|
1516
1692
|
const body = {
|
|
1517
1693
|
rowKey: args.row ? String(args.row) : void 0,
|
|
1518
1694
|
rowId: args.rowId ? String(args.rowId) : void 0,
|
|
@@ -1524,15 +1700,11 @@ async function emitPlanProgress(args) {
|
|
|
1524
1700
|
remainingWork: args.remaining ? String(args.remaining) : void 0,
|
|
1525
1701
|
evidence: evidence.length ? evidence : void 0,
|
|
1526
1702
|
proposed: args.proposed === true || args.proposed === "true",
|
|
1527
|
-
executorRef: args.executorRef ? String(args.executorRef) :
|
|
1703
|
+
executorRef: args.executorRef ? String(args.executorRef) : provider
|
|
1528
1704
|
};
|
|
1529
1705
|
const res = await fetch(url, {
|
|
1530
1706
|
method: "POST",
|
|
1531
|
-
headers:
|
|
1532
|
-
"Content-Type": "application/json",
|
|
1533
|
-
"X-OpenClaw-Cron-Secret": secret,
|
|
1534
|
-
"X-Kynver-Runtime-Secret": secret
|
|
1535
|
-
},
|
|
1707
|
+
headers: buildHarnessCallbackHeaders(secret),
|
|
1536
1708
|
body: JSON.stringify(body)
|
|
1537
1709
|
});
|
|
1538
1710
|
const text = await res.text();
|
|
@@ -1549,7 +1721,7 @@ async function emitPlanProgress(args) {
|
|
|
1549
1721
|
console.log(JSON.stringify(parsed, null, 2));
|
|
1550
1722
|
}
|
|
1551
1723
|
async function verifyPlan(args) {
|
|
1552
|
-
const planId = required(args, "plan");
|
|
1724
|
+
const planId = required(args.plan ? String(args.plan) : void 0, "plan");
|
|
1553
1725
|
const slug = loadUserConfig().agentOsSlug;
|
|
1554
1726
|
if (!slug) {
|
|
1555
1727
|
console.error("requires agentOsSlug in ~/.kynver/config.json for verify (session route)");
|
|
@@ -1598,6 +1770,7 @@ function usage(code = 0) {
|
|
|
1598
1770
|
[
|
|
1599
1771
|
"Usage:",
|
|
1600
1772
|
" kynver login --api-key KEY",
|
|
1773
|
+
" kynver runner credential [--agent-os-id ID] [--base-url URL]",
|
|
1601
1774
|
" kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--max-workers N] [--provider claude|cursor]",
|
|
1602
1775
|
" kynver daemon --run RUN_ID --agent-os-id AOS_ID [--execute] [--interval-ms MS]",
|
|
1603
1776
|
" kynver run create --repo /path/repo [--name name] [--base origin/main]",
|
|
@@ -1621,7 +1794,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1621
1794
|
const scope = argv.shift();
|
|
1622
1795
|
let action;
|
|
1623
1796
|
let rest;
|
|
1624
|
-
if (scope === "run" || scope === "worker" || scope === "plan") {
|
|
1797
|
+
if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner") {
|
|
1625
1798
|
action = argv.shift();
|
|
1626
1799
|
rest = argv;
|
|
1627
1800
|
} else {
|
|
@@ -1632,6 +1805,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1632
1805
|
mkdirSync5(runsDir, { recursive: true });
|
|
1633
1806
|
mkdirSync5(worktreesDir, { recursive: true });
|
|
1634
1807
|
if (scope === "login") return void await runLogin(args);
|
|
1808
|
+
if (scope === "runner" && action === "credential") return void await mintRunnerCredential(args);
|
|
1635
1809
|
if (scope === "setup") return void await runSetup(args);
|
|
1636
1810
|
if (scope === "daemon") return void await runDaemon(args);
|
|
1637
1811
|
if (scope === "plan" && action === "progress") return void await emitPlanProgress(args);
|
|
@@ -1648,7 +1822,7 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
1648
1822
|
if (scope === "worker" && action === "complete") return void await completeWorker(args);
|
|
1649
1823
|
unknownCommand(scope, action);
|
|
1650
1824
|
}
|
|
1651
|
-
var isCliEntry = process.argv[1] &&
|
|
1825
|
+
var isCliEntry = process.argv[1] && path14.resolve(process.argv[1]) === path14.resolve(fileURLToPath(import.meta.url));
|
|
1652
1826
|
if (isCliEntry) {
|
|
1653
1827
|
void main().catch((error) => {
|
|
1654
1828
|
console.error(error);
|