@agentskit/harness 0.12.0 → 0.13.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.
- package/CHANGELOG.md +37 -0
- package/capabilities/public-surface.json +83 -83
- package/dist/cli.js +86 -34
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +13 -4
- package/dist/index.js +98 -50
- package/dist/index.js.map +1 -1
- package/docs/MODULE-BOUNDARIES.md +4 -3
- package/package.json +5 -1
- package/release/manifest.json +3 -3
- package/release/notes.md +12 -0
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { createHash, randomUUID, createPrivateKey, createPublicKey, sign, verify } from 'crypto';
|
|
3
|
-
import { readFileSync, mkdirSync, existsSync, unlinkSync, openSync, writeFileSync, closeSync, readdirSync, statSync, writeSync, appendFileSync, mkdtempSync, renameSync, rmSync } from 'fs';
|
|
3
|
+
import { readFileSync, mkdirSync, existsSync, unlinkSync, openSync, writeFileSync, closeSync, readdirSync, statSync, writeSync, appendFileSync, mkdtempSync, renameSync, rmSync, accessSync, constants } from 'fs';
|
|
4
4
|
import { resolve, dirname, relative, basename, join, extname, isAbsolute, delimiter, sep } from 'path';
|
|
5
5
|
import { Command } from 'commander';
|
|
6
6
|
import { execFile, spawn, execFileSync } from 'child_process';
|
|
@@ -970,9 +970,9 @@ var createDocBridgeContextProvider = ({ root, indexPath = ".doc-bridge/index.jso
|
|
|
970
970
|
const started = Date.now();
|
|
971
971
|
const ageBudget = maxAgeHours ?? 0;
|
|
972
972
|
const inspection = ageBudget > 0 ? inspectDocBridgeIndex(root, indexPath, now4()) : null;
|
|
973
|
-
if (inspection?.error)
|
|
973
|
+
if (inspection?.error) fail(`Doc Bridge index is unreadable: ${inspection.error}`, "INVALID_STATE");
|
|
974
974
|
if (inspection?.ageHours !== null && inspection?.ageHours !== void 0 && inspection.ageHours > ageBudget) {
|
|
975
|
-
|
|
975
|
+
fail(`Doc Bridge index is ${inspection.ageHours.toFixed(1)}h old; refresh it before resolving context.`, "STALE");
|
|
976
976
|
}
|
|
977
977
|
const document = index(root, indexPath);
|
|
978
978
|
const contentHash = sourceHash(document);
|
|
@@ -993,7 +993,9 @@ var createDocBridgeContextProvider = ({ root, indexPath = ".doc-bridge/index.jso
|
|
|
993
993
|
});
|
|
994
994
|
var executable = (path) => {
|
|
995
995
|
try {
|
|
996
|
-
|
|
996
|
+
if (!statSync(path).isFile()) return false;
|
|
997
|
+
accessSync(path, constants.X_OK);
|
|
998
|
+
return true;
|
|
997
999
|
} catch {
|
|
998
1000
|
return false;
|
|
999
1001
|
}
|
|
@@ -1524,7 +1526,7 @@ var validateIteration = (iteration, index2) => {
|
|
|
1524
1526
|
if (typeof result !== "object" || result === null || Array.isArray(result)) return fail(`iterations[${index2}].steps[${stepIndex}] must be an object.`, "INVALID_INPUT");
|
|
1525
1527
|
if (result.step !== IMPROVEMENT_CYCLE_STEPS[stepIndex]) return fail(`iterations[${index2}].steps[${stepIndex}] must be ${IMPROVEMENT_CYCLE_STEPS[stepIndex]}.`, "INVALID_INPUT");
|
|
1526
1528
|
if (!["passed", "failed", "blocked", "pending"].includes(result.status)) return fail(`iterations[${index2}].steps[${stepIndex}].status is invalid.`, "INVALID_INPUT");
|
|
1527
|
-
if (result.status !== "passed" &&
|
|
1529
|
+
if (result.status !== "passed" && (typeof result.reason !== "string" || !result.reason.trim())) return fail(`iterations[${index2}].steps[${stepIndex}].reason is required when the step does not pass.`, "INVALID_INPUT");
|
|
1528
1530
|
});
|
|
1529
1531
|
if (iteration.adjustment !== void 0) nonEmpty(iteration.adjustment, `iterations[${index2}].adjustment`);
|
|
1530
1532
|
return { ...iteration, metrics: validateMetrics(iteration.metrics, index2) };
|
|
@@ -1538,8 +1540,10 @@ var assessImprovementCycle = (input) => {
|
|
|
1538
1540
|
const iterations = input.iterations.map(validateIteration);
|
|
1539
1541
|
iterations.forEach((iteration, index2) => {
|
|
1540
1542
|
if (iteration.iteration !== index2 + 1) return fail("iterations must be sequential and start at 1.", "INVALID_INPUT");
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
+
const isLast = index2 === iterations.length - 1;
|
|
1544
|
+
const iterationComplete = iteration.steps.every((step) => step.status === "passed");
|
|
1545
|
+
if (iterationComplete && !isLast) return fail("a completed cycle cannot have later iterations.", "INVALID_INPUT");
|
|
1546
|
+
if (!isLast && !iteration.adjustment) return fail(`iterations[${index2}].adjustment is required before repeating.`, "INVALID_INPUT");
|
|
1543
1547
|
});
|
|
1544
1548
|
const matrix = iterations.map((iteration) => {
|
|
1545
1549
|
const statuses = Object.fromEntries(iteration.steps.map((step) => [step.step, step.status]));
|
|
@@ -2241,7 +2245,17 @@ var MODEL_ROLES = ["orchestrator", "reviewer", "builder", "watcher"];
|
|
|
2241
2245
|
|
|
2242
2246
|
// src/kernel/pii.ts
|
|
2243
2247
|
var PATTERNS = [
|
|
2244
|
-
|
|
2248
|
+
// PEM key blocks first: large, unambiguous, and must claim their content before any narrower pattern below
|
|
2249
|
+
// could otherwise match a substring inside the base64 body (unlikely, but claimed-range order matters).
|
|
2250
|
+
{ kind: "private-key", regex: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |ENCRYPTED |)PRIVATE KEY-----[\s\S]*?-----END (?:RSA |EC |DSA |OPENSSH |ENCRYPTED |)PRIVATE KEY-----/g },
|
|
2251
|
+
// `sk-` body allows `-`/`_` (not just alnum) so a project/scoped key like `sk-proj-...`/`sk-live-...` matches
|
|
2252
|
+
// as one token instead of the hyphen splitting it into a too-short fragment. `github_pat_` (fine-grained PAT)
|
|
2253
|
+
// and `AIza…` (Google API key) are current real-world formats missing from the original list entirely.
|
|
2254
|
+
{ kind: "api-key", regex: /\b(?:sk-[A-Za-z0-9_-]{16,}|sk_(?:live|test)_[A-Za-z0-9]{16,}|pk_(?:live|test)_[A-Za-z0-9]{16,}|gh[opsu]_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|AKIA[0-9A-Z]{16}|AIza[A-Za-z0-9_-]{30,}|xox[baprs]-[A-Za-z0-9-]{10,})\b/g },
|
|
2255
|
+
// The AWS *secret* half (as opposed to the `AKIA…` access-key id above) has no recognizable prefix — a bare
|
|
2256
|
+
// 40-char base64-shaped run is too generic to scan for on its own (matches hashes, tokens, arbitrary base64).
|
|
2257
|
+
// Anchoring on the conventional key name it's almost always assigned to/from keeps this pattern high-signal.
|
|
2258
|
+
{ kind: "api-key", regex: /\b(?:aws_secret_access_key|AWS_SECRET_ACCESS_KEY|SecretAccessKey)\s*[:=]\s*["']?([A-Za-z0-9/+=]{40})["']?/g },
|
|
2245
2259
|
{ kind: "email", regex: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g },
|
|
2246
2260
|
{ kind: "credit-card", regex: /\b(?:\d[ -]?){13,16}\b/g },
|
|
2247
2261
|
{ kind: "phone", regex: /\b\+?\d{1,3}?[\s().-]?\(?\d{2,4}\)?[\s.-]?\d{3,4}[\s.-]?\d{4}\b/g }
|
|
@@ -2389,7 +2403,9 @@ var exportEvidenceBundle = async ({ configPath, runId, outputPath, privateKeyPat
|
|
|
2389
2403
|
`, "utf8");
|
|
2390
2404
|
return bundle;
|
|
2391
2405
|
};
|
|
2392
|
-
var
|
|
2406
|
+
var EVIDENCE_MAX_FILE_BYTES = 25 * 1048576;
|
|
2407
|
+
var EVIDENCE_MAX_TOTAL_BYTES = 200 * 1048576;
|
|
2408
|
+
var verifyEvidenceBundle = (path, { trustedKeys = [], maxFileBytes = EVIDENCE_MAX_FILE_BYTES, maxTotalBytes = EVIDENCE_MAX_TOTAL_BYTES } = {}) => {
|
|
2393
2409
|
const bundle = parseBundle(path);
|
|
2394
2410
|
if (bundle.type !== "agentskit-harness-evidence-bundle" || bundle.schemaVersion !== EVIDENCE_BUNDLE_SCHEMA_VERSION || !bundle.runId || !validKeyId(bundle.signerKeyId) || !validDigest(bundle.payloadHash) || bundle.signature?.algorithm !== "ed25519" || bundle.signature.keyId !== bundle.signerKeyId || typeof bundle.signature.publicKeyPem !== "string" || typeof bundle.signature.signatureBase64 !== "string" || !Array.isArray(bundle.files)) fail("Evidence bundle metadata is invalid.", "HARNESS_ERROR");
|
|
2395
2411
|
if (trustedKeys.length) {
|
|
@@ -2399,10 +2415,14 @@ var verifyEvidenceBundle = (path, { trustedKeys = [] } = {}) => {
|
|
|
2399
2415
|
if (trusted.publicKeyPem !== bundle.signature.publicKeyPem) fail(`Evidence bundle key does not match trust store: ${bundle.signerKeyId}`, "HARNESS_ERROR");
|
|
2400
2416
|
}
|
|
2401
2417
|
const paths = /* @__PURE__ */ new Set();
|
|
2418
|
+
let totalBytes = 0;
|
|
2402
2419
|
for (const file of bundle.files) {
|
|
2403
2420
|
if (!file || typeof file.path !== "string" || paths.has(file.path) || !validDigest(file.sha256) || typeof file.contentBase64 !== "string") fail("Evidence bundle file metadata is invalid.", "HARNESS_ERROR");
|
|
2404
2421
|
paths.add(file.path);
|
|
2422
|
+
if (file.contentBase64.length > Math.ceil(maxFileBytes / 3) * 4) fail(`Evidence bundle file exceeds the maximum allowed size: ${file.path}`, "HARNESS_ERROR");
|
|
2405
2423
|
const content = Buffer.from(file.contentBase64, "base64");
|
|
2424
|
+
totalBytes += content.length;
|
|
2425
|
+
if (totalBytes > maxTotalBytes) fail("Evidence bundle exceeds the maximum total allowed size.", "HARNESS_ERROR");
|
|
2406
2426
|
if (sha256(content) !== file.sha256) fail(`Evidence bundle file hash mismatch: ${file.path}`, "HARNESS_ERROR");
|
|
2407
2427
|
}
|
|
2408
2428
|
if (!paths.has(`runs/${bundle.runId}/run.json`) || !paths.has(`runs/${bundle.runId}/events.ndjson`)) fail("Evidence bundle is missing the run projection or event log.", "HARNESS_ERROR");
|
|
@@ -4100,6 +4120,14 @@ var githubCommentExists = async (runner, input, options2 = {}) => {
|
|
|
4100
4120
|
const list2 = await ghJson(runner, ["api", "--paginate", `repos/${input.repo}/issues/${input.number}/comments`, "--jq", "[.[].body]"], options2);
|
|
4101
4121
|
return Array.isArray(list2) && list2.some((body2) => typeof body2 === "string" && body2.includes(input.marker));
|
|
4102
4122
|
};
|
|
4123
|
+
var writeJsonAtomic = (path, value) => {
|
|
4124
|
+
const dir = dirname(path);
|
|
4125
|
+
mkdirSync(dir, { recursive: true });
|
|
4126
|
+
const tmp = join(dir, `.${basename(path)}.${process.pid}.${Date.now()}.tmp`);
|
|
4127
|
+
writeFileSync(tmp, `${JSON.stringify(value, null, 2)}
|
|
4128
|
+
`, "utf8");
|
|
4129
|
+
renameSync(tmp, path);
|
|
4130
|
+
};
|
|
4103
4131
|
var clip = (text6, max) => text6.length <= max ? text6 : `${text6.slice(0, Math.max(0, max - 1))}\u2026`;
|
|
4104
4132
|
var createFileMemoryKvStore = (dir) => {
|
|
4105
4133
|
mkdirSync(dir, { recursive: true });
|
|
@@ -4309,9 +4337,7 @@ var readStoredContract = (stateDir, identifier) => {
|
|
|
4309
4337
|
};
|
|
4310
4338
|
var writeStoredContract = (stateDir, stored) => {
|
|
4311
4339
|
const path = contractPath(stateDir, stored.issue);
|
|
4312
|
-
|
|
4313
|
-
writeFileSync(path, `${JSON.stringify(stored, null, 2)}
|
|
4314
|
-
`, "utf8");
|
|
4340
|
+
writeJsonAtomic(path, stored);
|
|
4315
4341
|
return path;
|
|
4316
4342
|
};
|
|
4317
4343
|
var contractIsFresh = (stored, issue, reuseHours, now4, memoryDigest) => stored.issueUpdatedAt === issue.updatedAt && (reuseHours === 0 || now4.getTime() - Date.parse(stored.generatedAt) <= reuseHours * 36e5) && (memoryDigest === void 0 || (stored.memoryDigest ?? hashJson([])) === memoryDigest);
|
|
@@ -4745,26 +4771,56 @@ var readDispatchRecord = (stateDir, identifier) => {
|
|
|
4745
4771
|
return null;
|
|
4746
4772
|
}
|
|
4747
4773
|
};
|
|
4748
|
-
var writeJson2 = (path, value) => {
|
|
4749
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
4750
|
-
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
4751
|
-
`, "utf8");
|
|
4752
|
-
};
|
|
4753
4774
|
var writeDispatchRecord = (stateDir, record3) => {
|
|
4754
4775
|
const path = dispatchRecordPath(stateDir, record3.issue);
|
|
4755
|
-
|
|
4776
|
+
writeJsonAtomic(path, record3);
|
|
4756
4777
|
return path;
|
|
4757
4778
|
};
|
|
4758
4779
|
var EVENTS_ROTATE_AT_BYTES = 10 * 1024 * 1024;
|
|
4780
|
+
var EVENTS_LOCK_STALE_MS = 5e3;
|
|
4781
|
+
var EVENTS_LOCK_MAX_ATTEMPTS = 100;
|
|
4782
|
+
var EVENTS_LOCK_RETRY_MS = 10;
|
|
4783
|
+
var acquireEventsLock = (lockFilePath) => {
|
|
4784
|
+
for (let attempt = 0; attempt < EVENTS_LOCK_MAX_ATTEMPTS; attempt += 1) {
|
|
4785
|
+
try {
|
|
4786
|
+
return openSync(lockFilePath, "wx");
|
|
4787
|
+
} catch (error) {
|
|
4788
|
+
if (error.code !== "EEXIST") throw error;
|
|
4789
|
+
try {
|
|
4790
|
+
if (Date.now() - statSync(lockFilePath).mtimeMs > EVENTS_LOCK_STALE_MS) unlinkSync(lockFilePath);
|
|
4791
|
+
} catch {
|
|
4792
|
+
}
|
|
4793
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, EVENTS_LOCK_RETRY_MS);
|
|
4794
|
+
}
|
|
4795
|
+
}
|
|
4796
|
+
return null;
|
|
4797
|
+
};
|
|
4759
4798
|
var appendLoopEvent = (stateDir, event2, bus, now4 = () => /* @__PURE__ */ new Date()) => {
|
|
4760
4799
|
const path = join(stateDir, "events.ndjson");
|
|
4761
4800
|
mkdirSync(dirname(path), { recursive: true });
|
|
4801
|
+
const lockFilePath = `${path}.lock`;
|
|
4802
|
+
const lockFd = acquireEventsLock(lockFilePath);
|
|
4762
4803
|
try {
|
|
4763
|
-
if (
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4804
|
+
if (lockFd !== null) {
|
|
4805
|
+
try {
|
|
4806
|
+
if (statSync(path).size > EVENTS_ROTATE_AT_BYTES) renameSync(path, join(stateDir, `events-archive-${now4().getTime()}.ndjson`));
|
|
4807
|
+
} catch {
|
|
4808
|
+
}
|
|
4809
|
+
}
|
|
4810
|
+
appendFileSync(path, `${JSON.stringify(event2)}
|
|
4767
4811
|
`, "utf8");
|
|
4812
|
+
} finally {
|
|
4813
|
+
if (lockFd !== null) {
|
|
4814
|
+
try {
|
|
4815
|
+
closeSync(lockFd);
|
|
4816
|
+
} catch {
|
|
4817
|
+
}
|
|
4818
|
+
try {
|
|
4819
|
+
unlinkSync(lockFilePath);
|
|
4820
|
+
} catch {
|
|
4821
|
+
}
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4768
4824
|
if (bus && typeof event2["type"] === "string") bus.emit(event2);
|
|
4769
4825
|
};
|
|
4770
4826
|
var gatherLoopState = async (input) => {
|
|
@@ -5060,7 +5116,7 @@ The loop will not retry this issue until you remove the \`${config.resilience.pa
|
|
|
5060
5116
|
if (!launched.accepted) notes.push(`${detail.identifier}: terminal ${launched.terminal} did not confirm the brief; deliver will nudge it if it stays idle`);
|
|
5061
5117
|
ledger.recordDispatch({ lease: claim.lease, idempotencyKey: plan.idempotencyKey, commandDigest: plan.commandDigest });
|
|
5062
5118
|
const record3 = { issue: detail.identifier, worktreeId: created.id, worktree, branch: actualBranch, terminal: launched.terminal, provider: builder.provider, model: builder.model, contractDigest: stored.digest, leaseKey: claim.lease.key, leaseId: claim.lease.leaseId, dispatchedAt: now4().toISOString(), url: detail.url, briefDigest, skills: skillRefs(pinnedSkills), setup: setupResult, effort: builder.effort, initialRemainingPercent: builder.remainingPercent, worktreePath: created.path };
|
|
5063
|
-
|
|
5119
|
+
writeJsonAtomic(dispatchRecordPath(loaded.stateDir, detail.identifier), record3);
|
|
5064
5120
|
appendLoopEvent(loaded.stateDir, { at: record3.dispatchedAt, type: "worker.dispatched", ...record3, command: builder.tui, briefAccepted: launched.accepted, tuiIdle: launched.idle }, bus);
|
|
5065
5121
|
await bus.runHook("afterDispatch", { issue: detail.identifier, provider: record3.provider, model: record3.model, branch: record3.branch, worktreeId: record3.worktreeId });
|
|
5066
5122
|
clearIssueFailures(loaded.stateDir, detail.identifier);
|
|
@@ -5177,11 +5233,6 @@ var discoverIntake = async (runner, input, options2 = {}) => {
|
|
|
5177
5233
|
// src/loop/deliver.ts
|
|
5178
5234
|
var message3 = (error) => error instanceof HarnessError ? `${error.code}: ${error.message}` : error instanceof Error ? error.message : String(error);
|
|
5179
5235
|
var isMissingOrcaWorktree = (error) => message3(error).includes("selector_not_found");
|
|
5180
|
-
var writeJson3 = (path, value) => {
|
|
5181
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
5182
|
-
writeFileSync(path, `${JSON.stringify(value, null, 2)}
|
|
5183
|
-
`, "utf8");
|
|
5184
|
-
};
|
|
5185
5236
|
var deliveryStatePath = (stateDir, identifier) => join(stateDir, "issues", identifier, "delivery.json");
|
|
5186
5237
|
var readDeliveryState = (stateDir, identifier) => {
|
|
5187
5238
|
const path = deliveryStatePath(stateDir, identifier);
|
|
@@ -5208,7 +5259,7 @@ var minutesBetween = (later, earlier) => earlier === null ? Number.POSITIVE_INFI
|
|
|
5208
5259
|
var orcaOptions = (config) => ({ bin: config.orca.bin, timeoutMs: config.orca.timeoutMs });
|
|
5209
5260
|
var linearOptions = (config) => ({ bin: config.orca.bin, workspaceId: config.linear.workspaceId, orca: { timeoutMs: config.orca.timeoutMs } });
|
|
5210
5261
|
var saveState = (ctx, state) => {
|
|
5211
|
-
if (!ctx.dryRun)
|
|
5262
|
+
if (!ctx.dryRun) writeJsonAtomic(deliveryStatePath(ctx.loaded.stateDir, state.issue), state);
|
|
5212
5263
|
};
|
|
5213
5264
|
var event = (ctx, payload) => {
|
|
5214
5265
|
if (!ctx.dryRun) appendLoopEvent(ctx.loaded.stateDir, { at: ctx.now().toISOString(), ...payload }, ctx.bus);
|
|
@@ -5880,7 +5931,7 @@ var runDeliver = async (input) => {
|
|
|
5880
5931
|
const candidates = (await githubOpenPullRequests(input.runner, { repo: config.project.repo, limit: 100 })).filter((item) => item.headRef === record3.branch || item.headRef.endsWith(`/${record3.worktree}`) || item.headRef === record3.worktree);
|
|
5881
5932
|
if (candidates.length) {
|
|
5882
5933
|
open = candidates;
|
|
5883
|
-
if (!dryRun)
|
|
5934
|
+
if (!dryRun) writeJsonAtomic(dispatchRecordPath(loaded.stateDir, record3.issue), { ...record3, branch: candidates[0].headRef });
|
|
5884
5935
|
notes.push(`${record3.issue}: PR found on branch ${candidates[0].headRef}; dispatch record updated`);
|
|
5885
5936
|
}
|
|
5886
5937
|
}
|
|
@@ -6489,7 +6540,7 @@ var parseSince = (value, now4) => {
|
|
|
6489
6540
|
return new Date(now4.getTime() - amount * unit);
|
|
6490
6541
|
}
|
|
6491
6542
|
const parsed = Date.parse(value);
|
|
6492
|
-
if (Number.isNaN(parsed))
|
|
6543
|
+
if (Number.isNaN(parsed)) fail(`Unrecognised --since value: ${value} (use 7d, 12h, 30m or an ISO date)`, "INVALID_INPUT");
|
|
6493
6544
|
return new Date(parsed);
|
|
6494
6545
|
};
|
|
6495
6546
|
var median2 = (values) => {
|
|
@@ -6738,6 +6789,7 @@ var phaseOf = (dispatch, delivery2) => {
|
|
|
6738
6789
|
return "in-flight";
|
|
6739
6790
|
};
|
|
6740
6791
|
var summarize2 = (phase, delivery2, dispatch) => {
|
|
6792
|
+
if (phase === "idle") return "Not yet dispatched";
|
|
6741
6793
|
if (phase === "merged") return `Merged PR #${delivery2.prNumber ?? "?"}`;
|
|
6742
6794
|
if (phase === "held" || phase === "held-incomplete-review") {
|
|
6743
6795
|
if (delivery2.heldFor) return `Held for a human (self-edit or protected path at ${delivery2.heldFor.slice(0, 7)})`;
|
|
@@ -6824,7 +6876,7 @@ var buildDebriefReport = (input) => {
|
|
|
6824
6876
|
});
|
|
6825
6877
|
continue;
|
|
6826
6878
|
}
|
|
6827
|
-
continue;
|
|
6879
|
+
if (!input.issue) continue;
|
|
6828
6880
|
}
|
|
6829
6881
|
rows.push(rowFor({ issue, dispatch, delivery: delivery2, intent, repo: config.project.repo, now: now4 }));
|
|
6830
6882
|
}
|
|
@@ -6992,7 +7044,7 @@ var runObservability = async (input) => {
|
|
|
6992
7044
|
const events2 = readLoopEvents(loaded.stateDir).filter((event2) => Date.parse(event2.at) >= since.getTime() && Date.parse(event2.at) <= at.getTime());
|
|
6993
7045
|
const ledger = createDispatchLedger(loaded.stateDir);
|
|
6994
7046
|
const active = ledger.active();
|
|
6995
|
-
const missingDeliveryIssues = active.filter((lease) => !existsSync(deliveryStatePath(loaded.stateDir, lease.issue))).map((lease) => lease.issue);
|
|
7047
|
+
const missingDeliveryIssues = active.filter((lease) => !existsSync(deliveryStatePath(loaded.stateDir, lease.issue)) && !existsSync(dispatchRecordPath(loaded.stateDir, lease.issue))).map((lease) => lease.issue);
|
|
6996
7048
|
const records = listDispatched(loaded.stateDir);
|
|
6997
7049
|
const completed = records.map((record3) => ({ record: record3, state: readDeliveryState(loaded.stateDir, record3.issue) })).filter(({ state }) => state.finishedAt && Date.parse(state.finishedAt) >= since.getTime());
|
|
6998
7050
|
const leadTimes = completed.map(({ record: record3, state }) => state.finishedAt ? (Date.parse(state.finishedAt) - Date.parse(record3.dispatchedAt)) / 6e4 : null).filter((value) => value !== null && Number.isFinite(value)).sort((a, b) => a - b);
|
|
@@ -7515,7 +7567,7 @@ benchmark.command("baseline <taskId>").description("Record one controlled baseli
|
|
|
7515
7567
|
program.command("clean").description("Remove only configured task-owned temporary artifacts.").action(() => print(cleanTaskArtifacts(options().config)));
|
|
7516
7568
|
process.on("SIGINT", () => {
|
|
7517
7569
|
process.stderr.write("Cancelled.\n");
|
|
7518
|
-
process.
|
|
7570
|
+
process.exit(130);
|
|
7519
7571
|
});
|
|
7520
7572
|
try {
|
|
7521
7573
|
await program.parseAsync(process.argv);
|