@kici-dev/orchestrator 0.20.1 → 0.21.0

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.
@@ -491,6 +491,8 @@ export declare class AdminApiClient {
491
491
  workflowName?: string;
492
492
  repo?: string;
493
493
  since?: string;
494
+ /** Only runs whose id starts with these characters. */
495
+ runIdPrefix?: string;
494
496
  limit?: number;
495
497
  offset?: number;
496
498
  }): Promise<{
@@ -7,6 +7,8 @@ import { type AgentStepLogs } from '@kici-dev/engine';
7
7
  * recorded, or a log is stored and holds nothing. `null` when the step has
8
8
  * lines, or when the orchestrator does not report which case applies.
9
9
  */
10
- export declare function emptyStepLogNote(stepIndex: number, logs: Pick<AgentStepLogs, 'totalLines' | 'recorded'>): string | null;
10
+ export declare function emptyStepLogNote(
11
+ /** The step index, or an init step's `init[<n>]` label. */
12
+ step: number | string, logs: Pick<AgentStepLogs, 'totalLines' | 'recorded'>): string | null;
11
13
  export declare function registerRunsCommands(program: Command, getClient: () => AdminApiClient): void;
12
14
  //# sourceMappingURL=runs.d.ts.map
@@ -0,0 +1,4 @@
1
+ import type { AdminApiClient } from '../../api-client.js';
2
+ /** The full run id `ref` names; a prefix resolves through the run list. */
3
+ export declare function resolveRunRef(client: AdminApiClient, ref: string): Promise<string>;
4
+ //# sourceMappingURL=run-ref.d.ts.map
@@ -2,7 +2,7 @@
2
2
  * `kici-admin source` commands: webhook source CRUD. A secret flag takes a
3
3
  * literal value, `@/path/file`, `--from-env <VAR_NAME>` or `--stdin`.
4
4
  */
5
- import type { Command } from 'commander';
5
+ import { type Command } from 'commander';
6
6
  import type { AdminApiClient } from '../api-client.js';
7
7
  /** Satisfied by both the HTTP `GenericSourceResponse` and the DB `GenericWebhookSource`. */
8
8
  interface ListableGenericRow {
package/dist/cli.js CHANGED
@@ -8,7 +8,7 @@ import fs, { chmodSync, closeSync, constants, createReadStream, createWriteStrea
8
8
  import { Command, Option } from "commander";
9
9
  import { AGENT_DEFAULT_PORT, LOGGER_ENV_VARS, ORCHESTRATOR_DEFAULT_PORT, defineEnv, validateUnknownKiciVars } from "@kici-dev/shared/env";
10
10
  import { AgentPlatform, BaseColdStore, ChunkLru, PgTableAdapterBase, ReadinessStatus, addLogsToArchive, clearDispatchQueueDirect, computeMigrationsHash, createColdStoreAdminOps, createContextTemplateDirect, createDb, createDbRole, createLogger, createPool, createReadOnlyDbUser, createS3Client, decrypt, deleteContextDirect, deriveKey, diagnoseExitCode, diffFingerprints, dropAndCreateDatabase, emitKiciEventDirect, encrypt, ensureDatabase, fingerprintSchema, formatBytes, formatDiffEntry, formatUptime, isSchemaCurrent, kiciMkdtemp, listCheckRunTrackingDirect, listContextsDirect, listExecutionRunsDirect, listQueueDirect, listRegistrationsDirect, maskDatabaseUrl, parseDatabaseUrl, prunePeerCredentialsDirect, purgeContextsDirect, purgeScopedSecretsDirect, purgeSecretBackendsDirect, purgeStaleExecutionDirect, purgeStaleSourcesDirect, registerWorkflowManualDirect, resetRaftStateDirect, seedContextBindingDirect, seedContextDirect, serializeError, setContextPolicyDirect, setContextSecretDirect, sha256, showContextDirect, showExecutionRunDirect, showQueueEntryDirect, showRegistrationDirect, splitAgentPlatform, storeMigrationContentHash, toCommandError, toErrorMessage } from "@kici-dev/shared";
11
- import { AccessLogSource, ApprovalDecision, CiTrustLevel, ContextType, ExecutionJobStatus, FleetHostWriteRefusal, ForkPolicy, HoldType, JOIN_PROTOCOL_UNSUPPORTED_MESSAGE, JOIN_PROTOCOL_VERSION, JoinErrorCode, KICI_AGENT_ENV_PREFIX, KNOWN_ROLES, MinimumTrustSchema, OrchRole, OrchestratorMode, PLATFORM_CONNECTED_MODES, PRIVILEGED_ROOT_LABEL, PROTOCOL_VERSION, PeerForgetOutcome, RuntimeFact, ScalerBackendType, ScalerEventType, ScalerReloadOutcome, ScalerVmStatus, ScalerVmStopOutcome, ScalerVmTracker, TERMINAL_JOB_STATES, TERMINAL_RUN_STATES, WS_MAX_PAYLOAD_BYTES, accessLogWarmSqlCase, agentLabelOf, assertValidSecretKey, attestationVerifyStatusSchema, canonicalizeLabel, canonicalizeLabels, canonicalizeMatcher, flattenActor, formatExpiryDuration, getAccessLogColdDays, getSecretAuditLogColdDays, joinResponseSchema, matcherSatisfiedBy, minAccessLogWarmDays, minSecretAuditLogWarmDays, parseExpiryDuration, parseHostPropertyAssignments, platformToTaints, resolveHeldRunId, resolveRoleLabels, resourceRequestNestedSchema, runtimeLabel, scalerAgentLabels, scalerPlatformSchema, secretAuditLogWarmSqlCase, shouldRecordAccess, sleep, toIsoString } from "@kici-dev/engine";
11
+ import { AccessLogSource, ApprovalDecision, CiTrustLevel, ContextType, ExecutionJobStatus, FleetHostWriteRefusal, ForkPolicy, HoldType, JOIN_PROTOCOL_UNSUPPORTED_MESSAGE, JOIN_PROTOCOL_VERSION, JoinErrorCode, KICI_AGENT_ENV_PREFIX, KNOWN_ROLES, MinimumTrustSchema, OrchRole, OrchestratorMode, PLATFORM_CONNECTED_MODES, PRIVILEGED_ROOT_LABEL, PROTOCOL_VERSION, PeerForgetOutcome, RuntimeFact, ScalerBackendType, ScalerEventType, ScalerReloadOutcome, ScalerVmStatus, ScalerVmStopOutcome, ScalerVmTracker, StepPhase, TERMINAL_JOB_STATES, TERMINAL_RUN_STATES, WS_MAX_PAYLOAD_BYTES, accessLogWarmSqlCase, agentLabelOf, assertValidSecretKey, attestationVerifyStatusSchema, canonicalizeLabel, canonicalizeLabels, canonicalizeMatcher, compareStepsByPhase, flattenActor, formatExpiryDuration, getAccessLogColdDays, getSecretAuditLogColdDays, initStepIndex, initStepLabel, initStepType, invalidRunIdMessage, joinResponseSchema, matcherSatisfiedBy, minAccessLogWarmDays, minSecretAuditLogWarmDays, parseExpiryDuration, parseHostPropertyAssignments, parseInitStepRef, parseRunIdRef, pickRunIdMatch, platformToTaints, resolveHeldRunId, resolveRoleLabels, resourceRequestNestedSchema, runIdHasPrefix, runtimeLabel, scalerAgentLabels, scalerPlatformSchema, secretAuditLogWarmSqlCase, shouldRecordAccess, sleep, stepPhase, toIsoString } from "@kici-dev/engine";
12
12
  import crypto$1, { createDecipheriv, createHash, createHmac, createPublicKey, diffieHellman, generateKeyPairSync, hkdfSync, randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
13
13
  import { createInterface } from "node:readline";
14
14
  import { access, chmod, constants as constants$1, copyFile, link, mkdir, open, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
@@ -521,6 +521,7 @@ var AdminApiClient = class {
521
521
  workflowName: opts?.workflowName,
522
522
  repo: opts?.repo,
523
523
  since: opts?.since,
524
+ runIdPrefix: opts?.runIdPrefix,
524
525
  limit: opts?.limit || void 0,
525
526
  offset: opts?.offset || void 0
526
527
  });
@@ -18801,8 +18802,10 @@ function renderSourceListText(githubSources, genericSources, hasOrg, opts) {
18801
18802
  console.log("Tip: use --org <orgId> to also list generic sources.");
18802
18803
  }
18803
18804
  }
18804
- /** Matches the kici-admin global `--url` default. */
18805
- const DEFAULT_ORCH_URL = process.env.KICI_ADMIN_URL ?? DEFAULT_ADMIN_URL;
18805
+ /** Mirrors the kici-admin global `--url`: KICI_ADMIN_URL, read at parse time, else the default. */
18806
+ function baseUrlOption() {
18807
+ return new Option("--base-url <url>", "Orchestrator base URL").env("KICI_ADMIN_URL").default(DEFAULT_ADMIN_URL);
18808
+ }
18806
18809
  /** Null when the row is not a valid local source. */
18807
18810
  function parseLocalConfig(s) {
18808
18811
  if (s.provider_type !== "local" || !s.git_config) return null;
@@ -19414,7 +19417,7 @@ function registerSourceCommands(program, getClient) {
19414
19417
  await getClient().disableGenericSource(id);
19415
19418
  console.log(`Generic source disabled: ${id}`);
19416
19419
  }));
19417
- src.command("trigger-local <id>").description("Trigger a run against a local source (reads HEAD when --ref/--sha omitted)").option("--event <event>", "push | pull_request", "push").option("--ref <ref>", "Git ref (default: repo HEAD branch)").option("--sha <sha>", "Commit SHA (default: repo HEAD)").option("--before <sha>", "Commit before the push; the changed files are before..sha (default: parent of --sha)").option("--repo-full-name <name>", "owner/name identifier used in the payload", "local/repo").option("--base-url <url>", "Orchestrator base URL", DEFAULT_ORCH_URL).action(cliAction(async (id, opts) => {
19420
+ src.command("trigger-local <id>").description("Trigger a run against a local source (reads HEAD when --ref/--sha omitted)").option("--event <event>", "push | pull_request", "push").option("--ref <ref>", "Git ref (default: repo HEAD branch)").option("--sha <sha>", "Commit SHA (default: repo HEAD)").option("--before <sha>", "Commit before the push; the changed files are before..sha (default: parent of --sha)").option("--repo-full-name <name>", "owner/name identifier used in the payload", "local/repo").addOption(baseUrlOption()).action(cliAction(async (id, opts) => {
19418
19421
  const { source } = await getClient().getGenericSource(id);
19419
19422
  const cfg = parseLocalConfig(source);
19420
19423
  if (!cfg) {
@@ -19441,7 +19444,7 @@ function registerSourceCommands(program, getClient) {
19441
19444
  console.log(`Triggered local source ${id}: HTTP ${status}`);
19442
19445
  if (status >= 400) process.exit(1);
19443
19446
  }));
19444
- src.command("install-hook <id>").description("Install a post-receive hook in the local source repo that triggers runs on push").option("--repo <path>", "Repo path (default: the source repoBasePath)").option("--base-url <url>", "Orchestrator base URL", DEFAULT_ORCH_URL).action(cliAction(async (id, opts) => {
19447
+ src.command("install-hook <id>").description("Install a post-receive hook in the local source repo that triggers runs on push").option("--repo <path>", "Repo path (default: the source repoBasePath)").addOption(baseUrlOption()).action(cliAction(async (id, opts) => {
19445
19448
  const { source } = await getClient().getGenericSource(id);
19446
19449
  const cfg = parseLocalConfig(source);
19447
19450
  if (!cfg) {
@@ -19617,6 +19620,33 @@ function registerWorkflowCommands(program, getClient) {
19617
19620
  }));
19618
19621
  }
19619
19622
  //#endregion
19623
+ //#region src/cli/commands/shared/run-ref.ts
19624
+ /** Resolve a `kici-admin runs` run id: a full id, or a unique prefix (like a git short SHA). */
19625
+ const AMBIGUOUS_SHOWN = 5;
19626
+ const FIND_HINT = "'kici-admin runs list' shows full run ids";
19627
+ /** The full run id `ref` names; a prefix resolves through the run list. */
19628
+ async function resolveRunRef(client, ref) {
19629
+ const parsed = parseRunIdRef(ref);
19630
+ if (parsed.kind === "uuid") return parsed.runId;
19631
+ if (parsed.kind === "invalid") throw new Error(invalidRunIdMessage(ref, parsed.reason, FIND_HINT));
19632
+ const { runs, total } = await client.listRuns({
19633
+ runIdPrefix: parsed.hex,
19634
+ limit: AMBIGUOUS_SHOWN
19635
+ });
19636
+ const ids = runs.map((r) => String(r.runId));
19637
+ const matches = ids.filter((id) => runIdHasPrefix(id, parsed.hex));
19638
+ if (matches.length < ids.length && total > ids.length) throw new Error(prefixUnsupported(ref));
19639
+ return pickRunIdMatch({
19640
+ ref,
19641
+ matches,
19642
+ unseen: total - ids.length,
19643
+ findHint: FIND_HINT
19644
+ });
19645
+ }
19646
+ function prefixUnsupported(ref) {
19647
+ return `This orchestrator cannot look up the run id prefix '${ref}'; pass the full run id (${FIND_HINT}).`;
19648
+ }
19649
+ //#endregion
19620
19650
  //#region src/cli/commands/runs.ts
19621
19651
  function formatDuration(ms) {
19622
19652
  if (ms == null) return "-";
@@ -19657,6 +19687,42 @@ function workflowProvenanceLines(run) {
19657
19687
  if (run.workflowBranch) lines.push(` Workflow branch: ${run.workflowBranch}`);
19658
19688
  return lines;
19659
19689
  }
19690
+ /**
19691
+ * A job's steps in run order: setup and init pseudo-steps first, whatever order the
19692
+ * orchestrator sent. An older orchestrator orders init steps by their stored index, last.
19693
+ */
19694
+ function stepsInRunOrder(steps) {
19695
+ return [...steps].sort(compareStepsByPhase);
19696
+ }
19697
+ /**
19698
+ * The `#` cell of a step row: the step index, or `init[<n>]` for an init step, whose
19699
+ * stored index is a high internal value and not a position among the steps.
19700
+ */
19701
+ function stepNumberCell(step) {
19702
+ const initIndex = initStepIndex(step.stepType);
19703
+ return initIndex === void 0 ? String(step.stepIndex) : initStepLabel(initIndex);
19704
+ }
19705
+ /** The `type` cell of a `runs show` step row: blank for a plain step, `init` for any init. */
19706
+ function stepTypeCell(stepType) {
19707
+ if (stepPhase(stepType) === StepPhase.enum.init) return StepPhase.enum.init;
19708
+ return stepType === "step" ? "" : stepType;
19709
+ }
19710
+ /**
19711
+ * The stored step index `runs logs --step` names. An init step is shown as `init[<n>]`,
19712
+ * not by its stored index, so that label resolves through the job's steps; any other
19713
+ * value passes through for the server to validate.
19714
+ */
19715
+ async function resolveStepRef(client, runId, jobId, step) {
19716
+ const ref = String(step);
19717
+ const initIndex = parseInitStepRef(ref);
19718
+ if (initIndex === void 0) return ref;
19719
+ const { jobs } = await client.getRunJobs(runId, { includeSteps: true });
19720
+ const job = jobs.find((j) => j.jobId === jobId);
19721
+ if (!job) throw new Error(`Job ${jobId} not found in run ${runId}`);
19722
+ const match = (job.steps ?? []).find((s) => s.stepType === initStepType(initIndex));
19723
+ if (!match) throw new Error(`Job ${jobId} has no ${initStepLabel(initIndex)} step`);
19724
+ return String(match.stepIndex);
19725
+ }
19660
19726
  /** The step index of a job's setup log: the clone, the install and the module load. */
19661
19727
  const SETUP_LOG_STEP_INDEX = -1;
19662
19728
  /**
@@ -19664,11 +19730,12 @@ const SETUP_LOG_STEP_INDEX = -1;
19664
19730
  * recorded, or a log is stored and holds nothing. `null` when the step has
19665
19731
  * lines, or when the orchestrator does not report which case applies.
19666
19732
  */
19667
- function emptyStepLogNote(stepIndex, logs) {
19733
+ function emptyStepLogNote(step, logs) {
19668
19734
  if (logs.totalLines > 0 || logs.recorded === void 0) return null;
19669
- if (stepIndex === SETUP_LOG_STEP_INDEX) return logs.recorded ? "(the setup log for this job is empty)" : "(no setup log recorded for this job)";
19670
- return logs.recorded ? `(the log for step ${stepIndex} is empty)` : `(no log recorded for step ${stepIndex})`;
19735
+ if (step === SETUP_LOG_STEP_INDEX) return logs.recorded ? "(the setup log for this job is empty)" : "(no setup log recorded for this job)";
19736
+ return logs.recorded ? `(the log for step ${step} is empty)` : `(no log recorded for step ${step})`;
19671
19737
  }
19738
+ const RUN_ID_ARG_HELP = "Run id, or its first 4 or more characters";
19672
19739
  function registerRunsCommands(program, getClient) {
19673
19740
  const runs = program.command("runs").description("Inspect execution runs, jobs, and steps");
19674
19741
  runs.command("list").description("List execution runs (admin API: /api/v1/admin/runs)").option("--status <statuses>", "Filter by run status. Accepts a single value or a comma-separated list (e.g. success,failed)").option("--workflow-name <name>", "Filter by workflow name. A run that spans several workflows matches any of them").option("--repo <ownerRepo>", "Filter by repo identifier (owner/repo)").option("--since <iso8601>", "Only include runs with created_at strictly later than this ISO-8601 timestamp").option("--count", "Return only the count of matching runs, skipping the row listing").option("--limit <n>", "Max results (default 20, max 100)", "20").option("--offset <n>", "Skip first N results", "0").option("--json", "Emit raw JSON instead of a table").action(cliAction(async (opts) => {
@@ -19723,7 +19790,8 @@ function registerRunsCommands(program, getClient) {
19723
19790
  console.log("");
19724
19791
  console.log(`Showing ${data.runs.length} of ${data.total} (offset ${data.offset}, limit ${data.limit})`);
19725
19792
  }));
19726
- runs.command("show <runId>").description("Show run detail with jobs and steps").option("--json", "Emit raw JSON instead of formatted output").action(cliAction(async (runId, opts) => {
19793
+ runs.command("show").argument("<runId>", RUN_ID_ARG_HELP).description("Show run detail with jobs and steps").option("--json", "Emit raw JSON instead of formatted output").action(cliAction(async (runRef, opts) => {
19794
+ const runId = await resolveRunRef(getClient(), runRef);
19727
19795
  const client = getClient();
19728
19796
  const [runRespRaw, jobsRespRaw] = await Promise.all([client.getRun(runId), client.getRunJobs(runId, { includeSteps: true })]);
19729
19797
  const runResp = runRespRaw;
@@ -19800,18 +19868,19 @@ function registerRunsCommands(program, getClient) {
19800
19868
  "duration",
19801
19869
  "type"
19802
19870
  ];
19803
- const stepRows = (job.steps ?? []).map((s) => [
19804
- String(s.stepIndex),
19871
+ const stepRows = stepsInRunOrder(job.steps ?? []).map((s) => [
19872
+ stepNumberCell(s),
19805
19873
  s.stepName,
19806
19874
  s.status,
19807
19875
  s.exitCode != null ? String(s.exitCode) : "-",
19808
19876
  formatDuration(s.durationMs),
19809
- s.stepType === "step" ? "" : s.stepType
19877
+ stepTypeCell(s.stepType)
19810
19878
  ]);
19811
19879
  console.log(renderTable(stepHeaders, stepRows));
19812
19880
  }
19813
19881
  }));
19814
- runs.command("structured <runId>").description("Show the provenance-tagged structured run result (agent read path; /structured)").option("--json", "Emit the raw AgentRunResult (untrusted envelopes preserved)").action(cliAction(async (runId, opts) => {
19882
+ runs.command("structured").argument("<runId>", RUN_ID_ARG_HELP).description("Show the provenance-tagged structured run result (agent read path; /structured)").option("--json", "Emit the raw AgentRunResult (untrusted envelopes preserved)").action(cliAction(async (runRef, opts) => {
19883
+ const runId = await resolveRunRef(getClient(), runRef);
19815
19884
  const result = await getClient().getRunStructured(runId);
19816
19885
  if (opts.json) {
19817
19886
  console.log(JSON.stringify(result, null, 2));
@@ -19854,8 +19923,8 @@ function registerRunsCommands(program, getClient) {
19854
19923
  "exit",
19855
19924
  "duration"
19856
19925
  ];
19857
- const stepRows = job.steps.map((s) => [
19858
- String(s.stepIndex),
19926
+ const stepRows = stepsInRunOrder(job.steps).map((s) => [
19927
+ stepNumberCell(s),
19859
19928
  uv(s.stepName),
19860
19929
  s.status,
19861
19930
  s.exitCode != null ? String(s.exitCode) : "-",
@@ -19864,7 +19933,8 @@ function registerRunsCommands(program, getClient) {
19864
19933
  console.log(renderTable(stepHeaders, stepRows));
19865
19934
  }
19866
19935
  }));
19867
- runs.command("jobs <runId>").description("List jobs for a run (admin API: /api/v1/admin/runs/:runId/jobs)").option("--include-steps", "Embed step list inside each job (default false)").option("--json", "Emit raw JSON instead of a table").action(cliAction(async (runId, opts) => {
19936
+ runs.command("jobs").argument("<runId>", RUN_ID_ARG_HELP).description("List jobs for a run (admin API: /api/v1/admin/runs/:runId/jobs)").option("--include-steps", "Embed step list inside each job (default false)").option("--json", "Emit raw JSON instead of a table").action(cliAction(async (runRef, opts) => {
19937
+ const runId = await resolveRunRef(getClient(), runRef);
19868
19938
  const response = await getClient().getRunJobs(runId, { includeSteps: Boolean(opts.includeSteps) });
19869
19939
  if (opts.json) {
19870
19940
  console.log(JSON.stringify(response, null, 2));
@@ -19907,8 +19977,8 @@ function registerRunsCommands(program, getClient) {
19907
19977
  "exit",
19908
19978
  "duration"
19909
19979
  ];
19910
- const stepRows = steps.map((s) => [
19911
- String(s.stepIndex),
19980
+ const stepRows = stepsInRunOrder(steps).map((s) => [
19981
+ stepNumberCell(s),
19912
19982
  s.stepName,
19913
19983
  s.status,
19914
19984
  s.exitCode != null ? String(s.exitCode) : "-",
@@ -19918,10 +19988,12 @@ function registerRunsCommands(program, getClient) {
19918
19988
  }
19919
19989
  }
19920
19990
  }));
19921
- runs.command("logs <runId>").description("Print a page of a step log (admin API: /api/v1/admin/runs/:runId/jobs/:jobId/steps/:i/logs)").requiredOption("--job <jobId>", "Job id (the dispatch_queue row id for an eval round)").option("--step <n>", "Step index (default 0; -1 is the job setup log)", "0").option("--limit <n>", "Max lines to return (default 500, server caps at 2000)", "500").option("--cursor <c>", "Line-offset cursor from a previous page").option("--json", "Emit raw JSON instead of plain lines").action(cliAction(async (runId, opts) => {
19991
+ runs.command("logs").argument("<runId>", RUN_ID_ARG_HELP).description("Print a page of a step log (admin API: /api/v1/admin/runs/:runId/jobs/:jobId/steps/:i/logs)").requiredOption("--job <jobId>", "Job id (the dispatch_queue row id for an eval round)").option("--step <n>", "Step index (default 0; -1 is the job setup log), or an init step as init[<n>]", "0").option("--limit <n>", "Max lines to return (default 500, server caps at 2000)", "500").option("--cursor <c>", "Line-offset cursor from a previous page").option("--json", "Emit raw JSON instead of plain lines").action(cliAction(async (runRef, opts) => {
19992
+ const runId = await resolveRunRef(getClient(), runRef);
19993
+ const stepIndex = await resolveStepRef(getClient(), runId, String(opts.job), opts.step);
19922
19994
  const params = new URLSearchParams({ limit: String(opts.limit) });
19923
19995
  if (opts.cursor) params.set("cursor", String(opts.cursor));
19924
- const path = `/api/v1/admin/runs/${encodeURIComponent(runId)}/jobs/${encodeURIComponent(String(opts.job))}/steps/${encodeURIComponent(String(opts.step))}/logs?${params.toString()}`;
19996
+ const path = `/api/v1/admin/runs/${encodeURIComponent(runId)}/jobs/${encodeURIComponent(String(opts.job))}/steps/${encodeURIComponent(stepIndex)}/logs?${params.toString()}`;
19925
19997
  const response = await getClient().get(path);
19926
19998
  if (opts.json) {
19927
19999
  console.log(JSON.stringify(response, null, 2));
@@ -19929,10 +20001,12 @@ function registerRunsCommands(program, getClient) {
19929
20001
  }
19930
20002
  for (const line of response.lines) console.log(line.value);
19931
20003
  if (response.nextCursor) console.error(`(more: re-run with --cursor ${response.nextCursor})`);
19932
- const note = emptyStepLogNote(parseInt(String(opts.step), 10), response);
20004
+ const initIndex = parseInitStepRef(String(opts.step));
20005
+ const note = emptyStepLogNote(initIndex === void 0 ? parseInt(stepIndex, 10) : initStepLabel(initIndex), response);
19933
20006
  if (note) console.error(note);
19934
20007
  }));
19935
- runs.command("ephemeral-key <runId>").description("Show whether the run-ephemeral key has been scrubbed yet").option("--json", "Emit raw JSON instead of plain text").action(cliAction(async (runId, opts) => {
20008
+ runs.command("ephemeral-key").argument("<runId>", RUN_ID_ARG_HELP).description("Show whether the run-ephemeral key has been scrubbed yet").option("--json", "Emit raw JSON instead of plain text").action(cliAction(async (runRef, opts) => {
20009
+ const runId = await resolveRunRef(getClient(), runRef);
19936
20010
  const response = await getClient().getRunEphemeralKey(runId);
19937
20011
  if (opts.json) {
19938
20012
  console.log(JSON.stringify(response, null, 2));
@@ -19946,7 +20020,8 @@ function registerRunsCommands(program, getClient) {
19946
20020
  console.log(`created_at: -`);
19947
20021
  }
19948
20022
  }));
19949
- runs.command("secret-outputs <runId>").description("List per-job secret outputs (masked by default; --reveal decrypts and audits)").option("--output-key <key>", "Filter to a single output_key").option("--reveal", "Decrypt and print plaintext values. Audited with actor=secret-outputs.reveal; requires secret.reveal permission").option("--json", "Emit raw JSON instead of a table").action(cliAction(async (runId, opts) => {
20023
+ runs.command("secret-outputs").argument("<runId>", RUN_ID_ARG_HELP).description("List per-job secret outputs (masked by default; --reveal decrypts and audits)").option("--output-key <key>", "Filter to a single output_key").option("--reveal", "Decrypt and print plaintext values. Audited with actor=secret-outputs.reveal; requires secret.reveal permission").option("--json", "Emit raw JSON instead of a table").action(cliAction(async (runRef, opts) => {
20024
+ const runId = await resolveRunRef(getClient(), runRef);
19950
20025
  if (opts.reveal) process.stderr.write("⚠ --reveal will decrypt secret values; this call is recorded in secret_audit_log.\n");
19951
20026
  const response = await getClient().getRunSecretOutputs(runId, {
19952
20027
  outputKey: opts.outputKey,
@@ -26881,7 +26956,7 @@ init_manifest();
26881
26956
  const ALL_PLATFORMS = AgentPlatform.options;
26882
26957
  /** The orchestrator's own version (single-version invariant) = the packaged agent version. */
26883
26958
  function resolveKiciVersion() {
26884
- return "0.20.1";
26959
+ return "0.21.0";
26885
26960
  }
26886
26961
  /** Parse the --platform value: default set | single | CSV | `all`. */
26887
26962
  function parsePlatforms(raw) {
@@ -30591,6 +30666,16 @@ function registerQueueCommands(program, getClient) {
30591
30666
  }
30592
30667
  //#endregion
30593
30668
  //#region src/cli/commands/execution.ts
30669
+ /**
30670
+ * The full run id `execution show` was given. Over the admin API a prefix resolves
30671
+ * like `kici-admin runs show`; the direct-database path takes a full id only.
30672
+ */
30673
+ async function resolveExecutionRunRef(ref, client) {
30674
+ if (client) return resolveRunRef(client, ref);
30675
+ const parsed = parseRunIdRef(ref);
30676
+ if (parsed.kind === "uuid") return parsed.runId;
30677
+ throw new Error(`Invalid run id '${ref}': --database-url needs a full run id. Drop --database-url to look up a prefix over the admin API ('kici-admin execution list' shows full run ids).`);
30678
+ }
30594
30679
  function printRunsTable(runs) {
30595
30680
  if (runs.length === 0) {
30596
30681
  console.log("No execution runs found.");
@@ -30662,8 +30747,9 @@ function registerExecutionCommands(program, getClient) {
30662
30747
  printJsonOr(opts.json, result, (r) => printRunsTable(r.runs));
30663
30748
  }
30664
30749
  }));
30665
- exec.command("show <runId>").description("Show one run + its jobs (by run_id)").option(DIRECT_DB_URL_FLAG, DIRECT_DB_URL_HELP).option("--json", "Emit JSON output").action(cliAction(async (runId, opts) => {
30750
+ exec.command("show <runId>").description("Show one run + its jobs (by run_id, or its first 4+ characters)").option(DIRECT_DB_URL_FLAG, DIRECT_DB_URL_HELP).option("--json", "Emit JSON output").action(cliAction(async (runRef, opts) => {
30666
30751
  const dbUrl = resolveDirectDbUrl(opts.databaseUrl);
30752
+ const runId = await resolveExecutionRunRef(runRef, dbUrl ? void 0 : getClient());
30667
30753
  const result = dbUrl ? await showExecutionRunDirect(dbUrl, { runId }) : await getClient().get(`/api/v1/admin/executions/${encodeURIComponent(runId)}`);
30668
30754
  printJsonOr(opts.json, result, (r) => printRunShow(r.run, r.jobs));
30669
30755
  }));
@@ -37130,7 +37216,7 @@ function routeRootArgs(root, args) {
37130
37216
  * build entry; the fallback covers a source run (tests, `tsx`), the same shape
37131
37217
  * `server.ts` and `standalone.ts` use for `ORCHESTRATOR_VERSION`.
37132
37218
  */
37133
- const CLI_VERSION = "0.20.1";
37219
+ const CLI_VERSION = "0.21.0";
37134
37220
  /**
37135
37221
  * Root command whose option parsing first routes argv through
37136
37222
  * {@link routeRootArgs}: a leading `--version` prints the CLI version, and an
@@ -37153,7 +37239,7 @@ var KiciAdminProgram = class extends Command {
37153
37239
  */
37154
37240
  function buildProgram() {
37155
37241
  const program = new KiciAdminProgram();
37156
- program.name("kici-admin").description("KiCI orchestrator admin CLI for managing config, secrets, and tokens").version(CLI_VERSION, `-V, ${ROOT_VERSION_FLAG}`, "output the version number (--version also works before a command)").option("-u, --url <url>", "Orchestrator URL", process.env.KICI_ADMIN_URL ?? DEFAULT_ADMIN_URL).option("-t, --token <token>", "Admin API token", process.env.KICI_ADMIN_TOKEN);
37242
+ program.name("kici-admin").description("KiCI orchestrator admin CLI for managing config, secrets, and tokens").version(CLI_VERSION, `-V, ${ROOT_VERSION_FLAG}`, "output the version number (--version also works before a command)").addOption(new Option("-u, --url <url>", "Orchestrator URL").env("KICI_ADMIN_URL").default(DEFAULT_ADMIN_URL)).addOption(new Option("-t, --token <token>", "Admin API token").env("KICI_ADMIN_TOKEN"));
37157
37243
  /** Lazy, so --help works without --token. */
37158
37244
  function getClient() {
37159
37245
  const opts = program.opts();
@@ -652,9 +652,9 @@ export interface ExecutionJobTable {
652
652
  /** Job name (e.g. "test", "test[node-18]") */
653
653
  job_name: string;
654
654
  /**
655
- * The workflow that defines the job. A run can span several workflows (a
656
- * fixture run with no `--workflow`), so the run's own `workflow_name` does not
657
- * name it. NULL on a row written before the column existed.
655
+ * The workflow that defines the job. A run can span several workflows (an older `kici`'s shared
656
+ * fixture run), so the run's own
657
+ * `workflow_name` does not name it. NULL on a row written before the column existed.
658
658
  */
659
659
  workflow_name: ColumnType<string | null, string | null | undefined, string | null>;
660
660
  /** Job status: an `ExecutionJobStatus` value (engine). */
package/dist/index.js CHANGED
@@ -2793,6 +2793,7 @@ var AdminApiClient = class {
2793
2793
  workflowName: opts?.workflowName,
2794
2794
  repo: opts?.repo,
2795
2795
  since: opts?.since,
2796
+ runIdPrefix: opts?.runIdPrefix,
2796
2797
  limit: opts?.limit || void 0,
2797
2798
  offset: opts?.offset || void 0
2798
2799
  });
@@ -199,9 +199,10 @@ export interface WorkflowDispatchContext {
199
199
  */
200
200
  runRegisteredBeforeDispatch?: boolean;
201
201
  /**
202
- * Called each time this dispatch registers the run with the tracker. A
203
- * fixture run dispatches several workflows under one run id, and holds the
204
- * run open from the first registration until every workflow has registered.
202
+ * Called each time this dispatch registers the run with the tracker. An older `kici`'s shared
203
+ * fixture run dispatches several
204
+ * workflows under one run id, and holds the run open from the first
205
+ * registration until every workflow has registered.
205
206
  */
206
207
  onRunRegistered?: () => void;
207
208
  /**
@@ -433,7 +434,8 @@ interface JobEnvEvalResult {
433
434
  * Synthetic job-id prefix every needs-gate site stamps on a held-back job. The
434
435
  * release path (`dispatchReadyJob` → `findSyntheticJobId` → `addJobsToRun`)
435
436
  * keys on it, so it soundly recovers the gated set without threading a parallel
436
- * list through both the single-orch and cluster paths. Owned by `db/synthetic-job-ids.ts` so the `contexts/`
437
+ * list through both the single-orch and cluster paths. Owned by `db/synthetic-job-ids.ts` so the
438
+ * `contexts/`
437
439
  * concurrency sweep need not import this module.
438
440
  */
439
441
  export { NEEDS_PENDING_JOB_ID_PREFIX };
@@ -20,6 +20,10 @@
20
20
  * decorating secret resolver), and the fixture's `secrets` context mapping
21
21
  * resolves into namespaced secrets;
22
22
  * - direct workflow execution (bypass trigger matching) is supported.
23
+ *
24
+ * Like a webhook, it creates one run per matched workflow. A caller that
25
+ * follows a single run (no `acceptsMultipleRuns`) gets one shared run instead,
26
+ * and only when the workflows' job names cannot collide in it.
23
27
  */
24
28
  import { type ProcessingDeps } from './processor.js';
25
29
  import { type ActorPrincipal } from '@kici-dev/engine';
@@ -79,9 +83,18 @@ export interface TestTriggerInput {
79
83
  * here against the matched workflow's lock dispatch descriptor.
80
84
  */
81
85
  dispatchInputs?: Record<string, string>;
86
+ /**
87
+ * The caller follows every run in the result's `runs`. Without it, several
88
+ * matched workflows share one run, or the trigger is refused when their job
89
+ * names collide.
90
+ */
91
+ acceptsMultipleRuns?: boolean;
82
92
  }
83
93
  interface TestTriggerResult {
94
+ /** The first run's id: the only run when the caller follows one. */
84
95
  runId: string;
96
+ /** One entry per run, each executing one matched workflow. Absent for a shared run. */
97
+ runs?: TestTriggeredRun[];
85
98
  status: 'accepted' | 'rejected';
86
99
  reason?: string;
87
100
  jobIds: string[];
@@ -100,9 +113,16 @@ export declare function repoIdentityFromInlineInput(input: TestTriggerInput): {
100
113
  repoIdentifier: string;
101
114
  provider: string;
102
115
  };
116
+ /** One run the trigger created, with the matched workflow it executes. */
117
+ export interface TestTriggeredRun {
118
+ runId: string;
119
+ workflowName: string;
120
+ jobIds: string[];
121
+ }
103
122
  /**
104
123
  * Process a test trigger through the shared dispatch core: one
105
- * `WorkflowDispatchContext` per matched workflow, into `dispatchMatchedWorkflow`.
124
+ * `WorkflowDispatchContext` per matched workflow, into `dispatchMatchedWorkflow`,
125
+ * each under its own run id.
106
126
  */
107
127
  export declare function processTestTrigger(input: TestTriggerInput, deps: ProcessingDeps): Promise<TestTriggerResult>;
108
128
  export {};
@@ -192,6 +192,8 @@ interface UpdateStepProgressOptions {
192
192
  jobName: string;
193
193
  stepIndex: number;
194
194
  stepName: string;
195
+ /** The step's `step_type`, when the agent sent one. */
196
+ stepType?: string;
195
197
  state: 'running' | 'success' | 'failed' | 'skipped' | 'cancelled';
196
198
  durationMs?: number;
197
199
  installationId?: number;
@@ -42,7 +42,19 @@ export interface StepProgressEntry {
42
42
  name: string;
43
43
  status: 'pending' | 'running' | 'success' | 'failed' | 'skipped' | 'cancelled';
44
44
  durationMs?: number;
45
+ /** The step's stored index. An entry without one is read by its position (defensive). */
46
+ stepIndex?: number;
47
+ /** The step's `step_type`; places setup and init pseudo-steps first. */
48
+ stepType?: string;
45
49
  }
50
+ /**
51
+ * Record one step's progress: replace the entry for its index, or add it in display
52
+ * order. Pseudo-steps have high indices (init at 1_000_000 + n), so the list is keyed
53
+ * by index rather than position; a positional list would pad every gap up to it.
54
+ */
55
+ export declare function applyStepProgress(steps: StepProgressEntry[], entry: StepProgressEntry & {
56
+ stepIndex: number;
57
+ }): StepProgressEntry[];
46
58
  interface BuildCheckRunSummaryOptions {
47
59
  jobName: string;
48
60
  stepResults: StepResultData[];
@@ -285,9 +285,9 @@ export interface TrackedJobRow {
285
285
  jobId: string;
286
286
  jobName: string;
287
287
  /**
288
- * The workflow that defines the job. A fixture run with no `--workflow` runs
289
- * every matched workflow under one run id, so the run's own workflow does not
290
- * name it. `onExecutionStarted` stamps its own workflow on a job without one;
288
+ * The workflow that defines the job. An older `kici`'s shared fixture run holds every matched
289
+ * workflow, so the run's own workflow
290
+ * does not name it. `onExecutionStarted` stamps its own workflow on a job without one;
291
291
  * `addJobsToRun` inherits it from the placeholder the job replaces.
292
292
  */
293
293
  workflowName?: string;
@@ -830,6 +830,13 @@ export declare class ExecutionTracker {
830
830
  dispatchedByFailureLifecycle?: boolean;
831
831
  /** Re-run lineage, when this run re-runs another. */
832
832
  rerunLineage?: RerunLineage;
833
+ /**
834
+ * Set for a `kici run` test run, so its init failure stays readable on the
835
+ * test-run status and log routes, which serve only test runs.
836
+ */
837
+ testRun?: {
838
+ fixtureId: string;
839
+ };
833
840
  }): Promise<void>;
834
841
  /**
835
842
  * Write one `failed` execution_runs row for a global eval round that never
@@ -946,6 +953,10 @@ export declare class ExecutionTracker {
946
953
  * {@link ExecutionTracker.recordGlobalEvalRoundFailureRun}).
947
954
  */
948
955
  dispatchRoutingKey?: string;
956
+ /** Set for a `kici run` test run, as on {@link ExecutionTracker.recordInitFailureRun}. */
957
+ testRun?: {
958
+ fixtureId: string;
959
+ };
949
960
  }): Promise<void>;
950
961
  /**
951
962
  * Claim a held global evaluation round for release (`held` → `pending`).
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * The workflows a run spans, read from its job rows.
3
3
  *
4
- * A fixture run with no `--workflow` runs every matched workflow under one run
5
- * id. `execution_runs.workflow_name` holds the first of them; each job row
4
+ * An older `kici`'s shared fixture run holds every
5
+ * matched workflow. `execution_runs.workflow_name` holds the first of them; each job row
6
6
  * records its own in `execution_jobs.workflow_name`.
7
7
  */
8
8
  import type { Kysely } from 'kysely';
@@ -3,26 +3,28 @@
3
3
  *
4
4
  * Sorting `execution_steps` by `step_index` alone puts a pseudo-step where its index landed,
5
5
  * not where it ran. That suits the cache pseudo-steps (indices above the real steps, mostly
6
- * bracketing the work), but a job's image build runs strictly FIRST.
6
+ * bracketing the work), but a job's image build and its init commands run strictly FIRST.
7
7
  *
8
8
  * The index cannot express this: `step.status` rejects a negative index (an agent sending one
9
9
  * to an older orchestrator is disconnected mid-job), and the check-run reporter
10
10
  * (`steps[stepIndex]`) and the dashboard (log fetching gated on `stepIndex >= 0`) treat a
11
- * negative index as absent. So the ordering is expressed by TYPE, here, once.
11
+ * negative index as absent. So the ordering is expressed by TYPE, as the engine's
12
+ * `StepPhase`, here, once.
12
13
  */
13
14
  import { type Expression } from 'kysely';
14
15
  /**
15
- * Sort key placing setup pseudo-steps (run before the job's real steps) first. Apply BEFORE
16
- * the `step_index` sort:
16
+ * Sort key placing steps by `StepPhase`: setup pseudo-steps, then init commands, then the
17
+ * job's steps. Apply BEFORE the `step_index` sort:
17
18
  *
18
19
  * ```ts
19
20
  * qb.orderBy(setupStepsFirst()).orderBy('step_index', 'asc')
20
21
  * ```
21
22
  *
22
23
  * Untyped steps and every real step share one key, so `step_index` alone orders each group.
24
+ * The init pattern matches `initStepIndex`: the prefix followed by digits only.
23
25
  */
24
26
  export declare function setupStepsFirst(): Expression<number>;
25
- /** In-memory equivalent, kept beside the SQL so the two agree on what "setup" means. */
27
+ /** In-memory equivalent over DB rows, kept beside the SQL so the two agree on the phases. */
26
28
  export declare function compareStepsForDisplay(a: {
27
29
  step_type?: string | null;
28
30
  step_index: number;
@@ -30,4 +32,6 @@ export declare function compareStepsForDisplay(a: {
30
32
  step_type?: string | null;
31
33
  step_index: number;
32
34
  }): number;
35
+ /** {@link compareStepsForDisplay} over untyped rows, such as cold-store rows read back from S3. */
36
+ export declare function compareStepRowsForDisplay(a: Record<string, unknown>, b: Record<string, unknown>): number;
33
37
  //# sourceMappingURL=step-display-order.d.ts.map