@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 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
- Set `OPENCLAW_CRON_SECRET` or `KYNVER_RUNTIME_SECRET` for server callbacks.
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 path13 from "node:path";
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 saveApiKey(apiKey) {
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({ apiKey }, null, 2)}
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 secret = argsSecret || process.env.KYNVER_RUNTIME_SECRET || process.env.OPENCLAW_CRON_SECRET;
147
- if (!secret) failConfig("requires --secret, KYNVER_RUNTIME_SECRET, or OPENCLAW_CRON_SECRET");
148
- return String(secret);
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). Advanced RAM tuning stays internal."
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 path14 = input.diskPath?.trim() || "/";
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(path14);
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: path14,
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
- path12.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
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) : void 0
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] && path13.resolve(process.argv[1]) === path13.resolve(fileURLToPath(import.meta.url));
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);