@akagilnc/pi-workflow-roles 0.1.3932 → 0.1.3975
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/CLAUDE.md +1 -1
- package/dist/acp-host/production-host.js +2389 -6018
- package/dist/analyst-gate-cycles-read.js +15 -19
- package/dist/audit-escalation.js +16 -46
- package/dist/auditor-dossier-tool.js +7 -62
- package/dist/collector-identity.js +0 -157
- package/dist/collector-ledger.js +2 -42
- package/dist/compliance-transport.js +40 -14
- package/dist/diarist-contracts.js +2 -40
- package/dist/doctor-contracts.js +39 -108
- package/dist/doctor-evidence.js +1 -5
- package/dist/engine-detour-tool.js +1 -1
- package/dist/gatekeeper-role.js +49 -38
- package/dist/headless-host/production-host.js +4814 -8450
- package/dist/host-contracts.js +0 -5
- package/dist/merger-contracts.js +4 -6
- package/dist/navigator-attendance.js +30 -52
- package/dist/navigator-invocation-identity.js +0 -1
- package/dist/navigator-public-session.js +2 -1
- package/dist/notary-contracts.js +4 -10
- package/dist/package-contracts/auditor-output.js +5 -23
- package/dist/package-contracts/fixer-output.js +25 -49
- package/dist/package-contracts/gatekeeper-output.js +3 -13
- package/dist/package-contracts/navigator-output.js +4 -7
- package/dist/package-contracts/reviewer-output.js +1 -95
- package/dist/package-contracts/terminating-tools.js +33 -144
- package/dist/package-contracts/worker-output.js +1 -3
- package/dist/public-cli/auto-resume.js +30 -68
- package/dist/public-cli/case-dossier-delivery.js +5 -17
- package/dist/public-cli/diarist-run.js +9 -4
- package/dist/public-cli/inspector-run.js +2 -8
- package/dist/public-cli/instruction-seat-run.js +3 -7
- package/dist/public-cli/invocation.js +5 -2
- package/dist/public-cli/main.js +6068 -6964
- package/dist/public-cli/notary-run.js +1 -8
- package/dist/public-cli/post-admission.js +314 -113
- package/dist/public-cli/run-lifecycle.js +14 -38
- package/dist/public-cli/settlement.js +209 -390
- package/dist/public-cli/terminal.js +51 -45
- package/dist/public-role-summons.js +1 -12
- package/dist/reviewer-dispatch.js +12 -258
- package/dist/submission-correctable-error.js +0 -12
- package/dist/submission-errors.js +6 -1
- package/dist/submission-ledger.js +125 -168
- package/dist/ticket-provenance-contracts.js +2 -49
- package/dist/ticket-provenance.js +82 -31
- package/extensions/role-runtime.ts +1 -6
- package/package.json +1 -1
- package/resources/836-deleted-machine-instruction-inventory.md +18 -0
- package/src/acp-host/role-turn-host.ts +8 -3
- package/src/analyst-gate-cycles-read.ts +17 -29
- package/src/audit-escalation.ts +20 -50
- package/src/auditor-dossier-tool.ts +5 -103
- package/src/canonical-skill-binding.ts +3 -2
- package/src/collector-identity.ts +0 -179
- package/src/collector-ledger.ts +3 -46
- package/src/collector-role.ts +32 -61
- package/src/collector-tool-schemas.ts +20 -14
- package/src/compliance-transport.ts +55 -17
- package/src/countersign-role.ts +17 -8
- package/src/diarist-contracts.ts +3 -36
- package/src/diarist-role.ts +13 -7
- package/src/diarist.ts +4 -16
- package/src/doctor-contracts.ts +73 -94
- package/src/doctor-evidence.ts +1 -1
- package/src/doctor-role.ts +3 -3
- package/src/engine-detour-tool.ts +4 -1
- package/src/external-host-turn-loop.ts +9 -3
- package/src/gatekeeper-pass-envelope.ts +7 -12
- package/src/gatekeeper-role.ts +56 -50
- package/src/gleaner-left-role.ts +4 -2
- package/src/headless-host/role-turn-host.ts +4 -5
- package/src/host-contracts.ts +3 -8
- package/src/judge-role.ts +19 -13
- package/src/merger-contracts.ts +10 -6
- package/src/merger-role.ts +1 -5
- package/src/navigator-attendance.ts +37 -74
- package/src/navigator-invocation-identity.ts +0 -6
- package/src/navigator-public-session.ts +2 -1
- package/src/notary-contracts.ts +7 -10
- package/src/package-contracts/auditor-output.ts +4 -21
- package/src/package-contracts/fixer-output.ts +34 -52
- package/src/package-contracts/gatekeeper-output.ts +5 -22
- package/src/package-contracts/navigator-output.ts +7 -7
- package/src/package-contracts/reviewer-output.ts +2 -150
- package/src/package-contracts/terminating-tools.ts +32 -147
- package/src/package-contracts/worker-output.ts +0 -1
- package/src/pi/adapter.ts +3 -8
- package/src/public-cli/auto-resume.ts +41 -94
- package/src/public-cli/case-dossier-delivery.ts +5 -15
- package/src/public-cli/countersign-run.ts +14 -6
- package/src/public-cli/diarist-run.ts +11 -9
- package/src/public-cli/inspector-run.ts +2 -18
- package/src/public-cli/instruction-seat-run.ts +3 -17
- package/src/public-cli/invocation.ts +5 -2
- package/src/public-cli/merger-run.ts +1 -1
- package/src/public-cli/notary-run.ts +1 -18
- package/src/public-cli/post-admission.ts +385 -115
- package/src/public-cli/run-lifecycle.ts +13 -44
- package/src/public-cli/settlement.ts +314 -562
- package/src/public-cli/terminal.ts +66 -72
- package/src/public-role-summons.ts +5 -30
- package/src/reviewer-dispatch.ts +29 -331
- package/src/reviewer-role.ts +16 -83
- package/src/role-envelope.ts +16 -24
- package/src/role-runtime-dependencies.ts +1 -6
- package/src/role-runtime.ts +65 -201
- package/src/submission-correctable-error.ts +0 -14
- package/src/submission-errors.ts +6 -1
- package/src/submission-ledger.ts +187 -187
- package/src/ticket-provenance-contracts.ts +4 -60
- package/src/ticket-provenance.ts +98 -32
- package/src/worker-role.ts +35 -36
- package/dist/activation-ledger-session.js +0 -105
- package/dist/activation-ledger.js +0 -139
- package/dist/activation-trace.js +0 -33
- package/dist/atomic-write.js +0 -23
- package/dist/auditor-role.js +0 -12
- package/dist/canonical-skill-binding.js +0 -61
- package/dist/collector-handbook.js +0 -142
- package/dist/collector-receipt.js +0 -107
- package/dist/collector-role.js +0 -566
- package/dist/collector-tool-schemas.js +0 -107
- package/dist/countersign-role.js +0 -28
- package/dist/diarist-role.js +0 -44
- package/dist/diarist.js +0 -66
- package/dist/doctor-role.js +0 -75
- package/dist/fixer-bash-seatbelt.js +0 -13
- package/dist/gatekeeper-pass-envelope.js +0 -77
- package/dist/gleaner-left-role.js +0 -26
- package/dist/inspector-role.js +0 -29
- package/dist/judge-role.js +0 -104
- package/dist/merger-role.js +0 -55
- package/dist/navigator-role.js +0 -12
- package/dist/notary-role.js +0 -134
- package/dist/oauth-keepalive.js +0 -158
- package/dist/pi/adapter.js +0 -223
- package/dist/reviewer-agent.js +0 -122
- package/dist/reviewer-child-executor.js +0 -194
- package/dist/reviewer-construction.js +0 -103
- package/dist/reviewer-execution-ledger.js +0 -166
- package/dist/reviewer-failure-diagnostic.js +0 -17
- package/dist/reviewer-role.js +0 -159
- package/dist/reviewer-scope-prompt.js +0 -21
- package/dist/reviewer-settlement.js +0 -66
- package/dist/reviewer-workspace.js +0 -132
- package/dist/role-runtime.js +0 -1604
- package/dist/stderr-jsonl.js +0 -26
- package/dist/tool-execution-observation.js +0 -141
- package/dist/worker-role.js +0 -345
- package/dist/worker-submission-gates.js +0 -305
- package/src/collector-receipt.ts +0 -140
- package/src/diagnostic-tail.ts +0 -18
- package/src/reviewer-agent.ts +0 -135
- package/src/reviewer-child-executor.ts +0 -252
- package/src/reviewer-construction.ts +0 -195
- package/src/reviewer-execution-ledger.ts +0 -212
- package/src/reviewer-failure-diagnostic.ts +0 -18
- package/src/reviewer-scope-prompt.ts +0 -24
- package/src/reviewer-settlement.ts +0 -74
- package/src/reviewer-workspace.ts +0 -105
- package/src/shape-unreadable-failure.ts +0 -49
|
@@ -65,11 +65,9 @@ function normalizeOfficerArg(raw) {
|
|
|
65
65
|
return undefined;
|
|
66
66
|
return OFFICER_ARG_ALIASES[raw.trim()];
|
|
67
67
|
}
|
|
68
|
-
/**
|
|
69
|
-
function
|
|
70
|
-
|
|
71
|
-
return [];
|
|
72
|
-
return value.filter((item) => typeof item === "string");
|
|
68
|
+
/** Findings as recorded — missing/non-array → []. */
|
|
69
|
+
function asFindings(value) {
|
|
70
|
+
return Array.isArray(value) ? value : [];
|
|
73
71
|
}
|
|
74
72
|
/**
|
|
75
73
|
* Optional non-empty dispatch reason. Trim only decides emptiness; a non-empty
|
|
@@ -102,11 +100,10 @@ function acceptedGateReceiptIds(rows) {
|
|
|
102
100
|
}
|
|
103
101
|
return accepted;
|
|
104
102
|
}
|
|
105
|
-
function
|
|
106
|
-
if (args === undefined
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
return args.status.trim();
|
|
103
|
+
function readAcceptedGateStatus(args) {
|
|
104
|
+
if (args === undefined)
|
|
105
|
+
return "";
|
|
106
|
+
return typeof args.status === "string" ? args.status : args.status === undefined ? "" : String(args.status);
|
|
110
107
|
}
|
|
111
108
|
function requireAcceptedGateSpan(rows, filePath) {
|
|
112
109
|
const span = extractSessionTimestampSpan(rows);
|
|
@@ -182,25 +179,24 @@ function extractAllAcceptedGateToolCalls(rows) {
|
|
|
182
179
|
function projectAcceptedGateCall(filePath, rows, call) {
|
|
183
180
|
// Continuous memory volumes carry many summons; span only this binding's interval.
|
|
184
181
|
const span = requireAcceptedGateSpan(intervalRowsForGateCall(rows, call.rowIndex), filePath);
|
|
185
|
-
const status =
|
|
186
|
-
const findings =
|
|
182
|
+
const status = readAcceptedGateStatus(call.args);
|
|
183
|
+
const findings = asFindings(call.args?.findings);
|
|
187
184
|
const findingsCount = findings.length;
|
|
188
185
|
const bindingFields = {
|
|
189
186
|
...(call.attemptEntryId === undefined ? {} : { attemptEntryId: call.attemptEntryId }),
|
|
190
187
|
...(call.parentSessionFile === undefined ? {} : { parentSessionFile: call.parentSessionFile }),
|
|
191
188
|
};
|
|
192
189
|
if (DISPATCH_TOOLS.has(call.toolName)) {
|
|
193
|
-
// Lawful province non-dispatch release — opens no round
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
}
|
|
197
|
-
// Pairing terminal is dispatch; unknown/non-contract status stays loud (#475).
|
|
190
|
+
// Lawful province non-dispatch release — opens no round (#597).
|
|
191
|
+
// #836 / #622: bounce|escalate|unknown status must not throw and kill parent settlement.
|
|
192
|
+
// Only a real dispatch pairs; everything else is omitted from pairing (原 payload 已在账本).
|
|
198
193
|
if (status !== "dispatch") {
|
|
199
|
-
|
|
194
|
+
return undefined;
|
|
200
195
|
}
|
|
201
196
|
const officer = normalizeOfficerArg(call.args?.officer);
|
|
202
197
|
if (officer === undefined) {
|
|
203
|
-
|
|
198
|
+
// Missing officer on a dispatch face — omit pairing; do not kill parent (#836).
|
|
199
|
+
return undefined;
|
|
204
200
|
}
|
|
205
201
|
const reason = optionalDispatchReason(call.args?.reason);
|
|
206
202
|
return {
|
package/dist/audit-escalation.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readableGateItem } from "./readable-gate-item.js";
|
|
2
|
+
import { GatekeeperDecisionError } from "./submission-errors.js";
|
|
2
3
|
export const AUDIT_ESCALATION_KIND = "audit_escalation";
|
|
3
4
|
// Live Navigator settlement may consume only the projection produced by this
|
|
4
5
|
// owner. Its private WeakSet cannot be authored by role output; persisted/
|
|
@@ -32,23 +33,10 @@ export function buildAuditEscalationResult(decision, deliveredOutput) {
|
|
|
32
33
|
if (Object.hasOwn(decision, "findings") && decision.findings !== undefined) {
|
|
33
34
|
auditOwned.findings = decision.findings;
|
|
34
35
|
}
|
|
35
|
-
const deliveredFields = deliveredOutput !== undefined &&
|
|
36
|
-
deliveredOutput !== null &&
|
|
37
|
-
typeof deliveredOutput === "object" &&
|
|
38
|
-
!Array.isArray(deliveredOutput)
|
|
39
|
-
? { ...deliveredOutput }
|
|
40
|
-
: {};
|
|
41
|
-
// Role output cannot fill an absent audit/officer-owned field.
|
|
42
|
-
delete deliveredFields.conflicts;
|
|
43
|
-
delete deliveredFields.auditDecisionGate;
|
|
44
|
-
if (Object.hasOwn(decision, "officer")) {
|
|
45
|
-
delete deliveredFields.reason;
|
|
46
|
-
delete deliveredFields.officer;
|
|
47
|
-
delete deliveredFields.findings;
|
|
48
|
-
}
|
|
49
36
|
const result = {
|
|
50
|
-
|
|
51
|
-
...
|
|
37
|
+
kind: AUDIT_ESCALATION_KIND,
|
|
38
|
+
...(deliveredOutput !== undefined ? { receipt: deliveredOutput } : {}),
|
|
39
|
+
audit: auditOwned,
|
|
52
40
|
};
|
|
53
41
|
AUDIT_ESCALATION_LIVE_REGISTRY.add(result);
|
|
54
42
|
return result;
|
|
@@ -59,39 +47,11 @@ export function isAuditEscalationProjection(value) {
|
|
|
59
47
|
return false;
|
|
60
48
|
return AUDIT_ESCALATION_LIVE_REGISTRY.has(value);
|
|
61
49
|
}
|
|
62
|
-
function humanDecisionText(decision, result) {
|
|
63
|
-
const officer = Object.hasOwn(decision, "officer")
|
|
64
|
-
? decision.officer
|
|
65
|
-
: undefined;
|
|
66
|
-
const lines = [
|
|
67
|
-
officer === undefined
|
|
68
|
-
? "Human decision required: compliance audit escalation."
|
|
69
|
-
: `Human decision required: ${officer} escalation.`,
|
|
70
|
-
];
|
|
71
|
-
if (officer !== undefined && typeof result.reason === "string") {
|
|
72
|
-
lines.push(`Reason: ${result.reason}`);
|
|
73
|
-
}
|
|
74
|
-
if (Array.isArray(result.conflicts)) {
|
|
75
|
-
lines.push("Conflicts:", ...result.conflicts.map((conflict) => `- ${readableGateItem(conflict)}`));
|
|
76
|
-
}
|
|
77
|
-
if (officer !== undefined && Array.isArray(result.findings) && result.findings.length > 0) {
|
|
78
|
-
lines.push("Findings:", ...result.findings.map((finding) => `- ${readableGateItem(finding)}`));
|
|
79
|
-
}
|
|
80
|
-
const gate = result.auditDecisionGate;
|
|
81
|
-
if (gate !== null && typeof gate === "object" && !Array.isArray(gate)) {
|
|
82
|
-
const record = gate;
|
|
83
|
-
if (typeof record.question === "string")
|
|
84
|
-
lines.push(`Question: ${record.question}`);
|
|
85
|
-
if (Array.isArray(record.options)) {
|
|
86
|
-
lines.push("Options:", ...record.options.map((option) => `- ${readableGateItem(option)}`));
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
return lines.join("\n");
|
|
90
|
-
}
|
|
91
50
|
export function projectAuditEscalation(decision, deliveredOutput) {
|
|
92
51
|
const details = buildAuditEscalationResult(decision, deliveredOutput);
|
|
52
|
+
const original = deliveredOutput !== undefined ? deliveredOutput : decision;
|
|
93
53
|
return {
|
|
94
|
-
content: [{ type: "text", text:
|
|
54
|
+
content: [{ type: "text", text: readableGateItem(original) }],
|
|
95
55
|
details,
|
|
96
56
|
terminate: true,
|
|
97
57
|
...(decision.usage === undefined ? {} : { usage: decision.usage }),
|
|
@@ -132,5 +92,15 @@ export async function disposeComplianceDecision(decision, handlers, deliveredOut
|
|
|
132
92
|
return await handlers.bounce(decision.violations);
|
|
133
93
|
case "escalate":
|
|
134
94
|
return await handlers.escalate(projectAuditEscalation(decision, deliveredOutput));
|
|
95
|
+
case "transport_failure":
|
|
96
|
+
if (handlers.transportFailure !== undefined) {
|
|
97
|
+
return await handlers.transportFailure(decision);
|
|
98
|
+
}
|
|
99
|
+
throw new GatekeeperDecisionError({
|
|
100
|
+
status: "transport_failure",
|
|
101
|
+
stage: "auditor",
|
|
102
|
+
reason: decision.diagnostic,
|
|
103
|
+
submission: decision.submissions ?? decision.terminal,
|
|
104
|
+
});
|
|
135
105
|
}
|
|
136
106
|
}
|
|
@@ -1,64 +1,18 @@
|
|
|
1
|
-
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
1
|
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
import { readableGateItem } from "./readable-gate-item.js";
|
|
4
2
|
import { Type } from "typebox";
|
|
5
3
|
const AUDITOR_DOSSIER_TOOL_NAME = "ak_get_run_dossier";
|
|
6
|
-
const GATE_SUBMISSION_CANDIDATE_FILE = "gate-submission-candidate.json";
|
|
7
|
-
function isRecord(value) {
|
|
8
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
9
|
-
}
|
|
10
4
|
function auditorRunDirectory(context) {
|
|
11
5
|
const sessionFile = context.sessionManager?.getSessionFile?.();
|
|
12
6
|
if (sessionFile === void 0) return void 0;
|
|
13
7
|
return resolve(dirname(dirname(sessionFile)));
|
|
14
8
|
}
|
|
15
|
-
function
|
|
16
|
-
const entries = [...context.sessionManager?.getEntries?.() ?? []];
|
|
17
|
-
for (let i = entries.length - 1; i >= 0; i--) {
|
|
18
|
-
const entry = entries[i];
|
|
19
|
-
if (!isRecord(entry) || entry.type !== "message" || !isRecord(entry.message)) continue;
|
|
20
|
-
const message = entry.message;
|
|
21
|
-
if (message.role !== "assistant" || !Array.isArray(message.content)) continue;
|
|
22
|
-
for (const part of message.content) {
|
|
23
|
-
if (isRecord(part) && part.type === "toolCall") {
|
|
24
|
-
return entry;
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return void 0;
|
|
29
|
-
}
|
|
30
|
-
function gateSubmissionCandidatePath(runDirectory) {
|
|
31
|
-
return join(runDirectory, "artifacts", GATE_SUBMISSION_CANDIDATE_FILE);
|
|
32
|
-
}
|
|
33
|
-
function readLatestSubmissionArguments(context) {
|
|
34
|
-
const leaf = readLatestToolCallLeaf(context);
|
|
35
|
-
if (!isRecord(leaf) || !isRecord(leaf.message) || !Array.isArray(leaf.message.content)) {
|
|
36
|
-
return void 0;
|
|
37
|
-
}
|
|
38
|
-
for (const part of leaf.message.content) {
|
|
39
|
-
if (isRecord(part) && part.type === "toolCall" && "arguments" in part) {
|
|
40
|
-
return part.arguments;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
return void 0;
|
|
44
|
-
}
|
|
45
|
-
function buildGateOfficerReviewInstruction(input) {
|
|
46
|
-
return readableGateItem(input.submission);
|
|
47
|
-
}
|
|
48
|
-
function persistGateSubmissionCandidate(runDirectory, context) {
|
|
49
|
-
const leaf = readLatestToolCallLeaf(context);
|
|
50
|
-
if (leaf === void 0) return void 0;
|
|
51
|
-
const path = gateSubmissionCandidatePath(runDirectory);
|
|
52
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
53
|
-
writeFileSync(path, `${JSON.stringify(leaf)}
|
|
54
|
-
`, "utf8");
|
|
55
|
-
return path;
|
|
56
|
-
}
|
|
57
|
-
function createAuditorDossierTool(runDirectory, options) {
|
|
9
|
+
function createAuditorDossierTool(runDirectory) {
|
|
58
10
|
return {
|
|
59
11
|
name: AUDITOR_DOSSIER_TOOL_NAME,
|
|
60
12
|
description: "\u5B9A\u4F4D\u672C\u5BA1\u8BA1\u5E2D\u7ED1\u5B9A\u7684 run \u5377\u5B97\u53CA\u5176\u8BC1\u636E\u5165\u53E3\u3002",
|
|
61
|
-
|
|
13
|
+
// #836 r16 class 3: execute() ignores `_params` entirely — a closed root
|
|
14
|
+
// only rejects the role for saying more.
|
|
15
|
+
parameters: Type.Object({}, { additionalProperties: true }),
|
|
62
16
|
async execute(_id, _params) {
|
|
63
17
|
if (runDirectory === void 0) {
|
|
64
18
|
return {
|
|
@@ -66,15 +20,12 @@ function createAuditorDossierTool(runDirectory, options) {
|
|
|
66
20
|
details: void 0
|
|
67
21
|
};
|
|
68
22
|
}
|
|
69
|
-
const sessionPath = join(runDirectory, "session", "session.jsonl");
|
|
70
|
-
const submissionCandidate = options?.submissionCandidate;
|
|
71
23
|
const details = {
|
|
72
24
|
runDirectory,
|
|
73
25
|
admittedRequest: join(runDirectory, "admitted-request.json"),
|
|
74
|
-
parentSessionCandidate:
|
|
26
|
+
parentSessionCandidate: join(runDirectory, "session", "session.jsonl"),
|
|
75
27
|
attachments: join(runDirectory, "attachments"),
|
|
76
|
-
artifacts: join(runDirectory, "artifacts")
|
|
77
|
-
...submissionCandidate === void 0 ? {} : { submissionCandidate }
|
|
28
|
+
artifacts: join(runDirectory, "artifacts")
|
|
78
29
|
};
|
|
79
30
|
return {
|
|
80
31
|
content: [{ type: "text", text: JSON.stringify(details) }],
|
|
@@ -85,12 +36,6 @@ function createAuditorDossierTool(runDirectory, options) {
|
|
|
85
36
|
}
|
|
86
37
|
export {
|
|
87
38
|
AUDITOR_DOSSIER_TOOL_NAME,
|
|
88
|
-
GATE_SUBMISSION_CANDIDATE_FILE,
|
|
89
39
|
auditorRunDirectory,
|
|
90
|
-
|
|
91
|
-
createAuditorDossierTool,
|
|
92
|
-
gateSubmissionCandidatePath,
|
|
93
|
-
persistGateSubmissionCandidate,
|
|
94
|
-
readLatestSubmissionArguments,
|
|
95
|
-
readLatestToolCallLeaf
|
|
40
|
+
createAuditorDossierTool
|
|
96
41
|
};
|
|
@@ -69,17 +69,6 @@ export class CollectorUnknownEvidenceError extends CorrectableSubmissionError {
|
|
|
69
69
|
this.name = "CollectorUnknownEvidenceError";
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
|
-
/**
|
|
73
|
-
* Evidence-binding failure for a finding pointer that resolves to the wrong kind
|
|
74
|
-
* of stored record, lacks a GitHub locator, or has no identity group — not a
|
|
75
|
-
* free-shape gate on the submission envelope.
|
|
76
|
-
*/
|
|
77
|
-
export class CollectorFindingsValidationError extends CorrectableSubmissionError {
|
|
78
|
-
constructor(message) {
|
|
79
|
-
super(message);
|
|
80
|
-
this.name = "CollectorFindingsValidationError";
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
72
|
/**
|
|
84
73
|
* #676 D6: non-OPEN targets keep collected materials and must not fire new review
|
|
85
74
|
* requests. Bounce the request as correctable so the seat can still seal output.
|
|
@@ -101,149 +90,3 @@ export class CollectorWaitWindowClosedError extends CorrectableSubmissionError {
|
|
|
101
90
|
this.name = "CollectorWaitWindowClosedError";
|
|
102
91
|
}
|
|
103
92
|
}
|
|
104
|
-
function candidateRecord(candidate) {
|
|
105
|
-
if (candidate === undefined || candidate === null || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
106
|
-
return undefined;
|
|
107
|
-
}
|
|
108
|
-
return candidate;
|
|
109
|
-
}
|
|
110
|
-
/** Canonical top-level keys the collector output projection understands. */
|
|
111
|
-
const COLLECTOR_OUTPUT_CANONICAL_KEYS = new Set([
|
|
112
|
-
"findings",
|
|
113
|
-
"unfinishedReasons",
|
|
114
|
-
"infrastructureFailure",
|
|
115
|
-
]);
|
|
116
|
-
/** Non-canonical own keys mean content was present but not under a projectable key. */
|
|
117
|
-
function hasNonCanonicalOwnKeys(record) {
|
|
118
|
-
for (const key of Object.keys(record)) {
|
|
119
|
-
if (!COLLECTOR_OUTPUT_CANONICAL_KEYS.has(key))
|
|
120
|
-
return true;
|
|
121
|
-
}
|
|
122
|
-
return false;
|
|
123
|
-
}
|
|
124
|
-
/**
|
|
125
|
-
* #641 chain① / #676: turn model-submitted finding pointer refs into receipt findings.
|
|
126
|
-
* Binds resolvable evidence references only — no pure shape rejection of the
|
|
127
|
-
* candidate envelope. Unknown refs, wrong kinds, missing GitHub locator, and
|
|
128
|
-
* missing identity group stay as binding failures. Unreadable findings content
|
|
129
|
-
* is not washed into "zero findings": projection facts record the gap so
|
|
130
|
-
* downstream can open the session 正本 (第 0 条 / #676 C).
|
|
131
|
-
*/
|
|
132
|
-
export function enrichCollectorFindings(input) {
|
|
133
|
-
// Candidate present but not a record — content existed, none projected (#676 C).
|
|
134
|
-
if (input.candidate !== undefined && input.candidate !== null && candidateRecord(input.candidate) === undefined) {
|
|
135
|
-
return { findingsSource: "unreadable", findingsProjectedCount: 0, findingsUnprojected: true };
|
|
136
|
-
}
|
|
137
|
-
const record = candidateRecord(input.candidate);
|
|
138
|
-
if (record === undefined) {
|
|
139
|
-
return { findingsSource: "absent", findingsProjectedCount: 0, findingsUnprojected: false };
|
|
140
|
-
}
|
|
141
|
-
if (!Object.hasOwn(record, "findings")) {
|
|
142
|
-
// Non-canonical top-level content is not "absent" — record the projection gap (#676 C).
|
|
143
|
-
if (hasNonCanonicalOwnKeys(record)) {
|
|
144
|
-
return { findingsSource: "unreadable", findingsProjectedCount: 0, findingsUnprojected: true };
|
|
145
|
-
}
|
|
146
|
-
return { findingsSource: "absent", findingsProjectedCount: 0, findingsUnprojected: false };
|
|
147
|
-
}
|
|
148
|
-
const rawFindings = record["findings"];
|
|
149
|
-
if (!Array.isArray(rawFindings)) {
|
|
150
|
-
// Key present but not an array — content exists, none projected. No shape bounce.
|
|
151
|
-
return { findingsSource: "unreadable", findingsProjectedCount: 0, findingsUnprojected: true };
|
|
152
|
-
}
|
|
153
|
-
const byEvidenceId = new Map(input.records.map((evidence) => [evidence.evidenceId, evidence]));
|
|
154
|
-
let projected = 0;
|
|
155
|
-
let unprojected = false;
|
|
156
|
-
for (const raw of rawFindings) {
|
|
157
|
-
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
|
|
158
|
-
unprojected = true;
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
const item = raw;
|
|
162
|
-
const evidenceId = item.evidenceId;
|
|
163
|
-
if (typeof evidenceId !== "string" || evidenceId.length === 0) {
|
|
164
|
-
// Present item without a bindable pointer — keep gap fact, do not fabricate.
|
|
165
|
-
unprojected = true;
|
|
166
|
-
continue;
|
|
167
|
-
}
|
|
168
|
-
const evidence = byEvidenceId.get(evidenceId);
|
|
169
|
-
if (evidence === undefined) {
|
|
170
|
-
throw new CollectorUnknownEvidenceError(evidenceId);
|
|
171
|
-
}
|
|
172
|
-
if (evidence.kind !== "review" && evidence.kind !== "issue_comment" && evidence.kind !== "review_comment") {
|
|
173
|
-
throw new CollectorFindingsValidationError(`通进司 finding 指针指向不可承 finding 的证据种类 ${evidence.kind}`);
|
|
174
|
-
}
|
|
175
|
-
if (evidence.githubId === undefined) {
|
|
176
|
-
throw new CollectorFindingsValidationError(`通进司 finding 指针证据 ${evidenceId} 缺少 GitHub id`);
|
|
177
|
-
}
|
|
178
|
-
const identity = evidence.machineIdentity ?? null;
|
|
179
|
-
const group = input.groups.find((candidateGroup) => identityKey(candidateGroup.identity) === identityKey(identity));
|
|
180
|
-
if (group === undefined) {
|
|
181
|
-
throw new CollectorFindingsValidationError(`通进司 finding 指针证据 ${evidenceId} 无归属身份组`);
|
|
182
|
-
}
|
|
183
|
-
// Bound finding keeps pointer; non-string category/summary are unprojected field gaps
|
|
184
|
-
// (pointer success ≠ full content projection) — #676 C / 3939511832.
|
|
185
|
-
const category = item.category;
|
|
186
|
-
const summary = item.summary;
|
|
187
|
-
if (Object.hasOwn(item, "category") && typeof category !== "string")
|
|
188
|
-
unprojected = true;
|
|
189
|
-
if (Object.hasOwn(item, "summary") && typeof summary !== "string")
|
|
190
|
-
unprojected = true;
|
|
191
|
-
group.findings.push({
|
|
192
|
-
identity,
|
|
193
|
-
source: {
|
|
194
|
-
kind: evidence.kind,
|
|
195
|
-
id: evidence.githubId,
|
|
196
|
-
evidenceId: evidence.evidenceId,
|
|
197
|
-
headRelation: headRelationFor(evidence, input.targetHead),
|
|
198
|
-
},
|
|
199
|
-
...(typeof category === "string" ? { category } : {}),
|
|
200
|
-
...(typeof summary === "string" ? { summary } : {}),
|
|
201
|
-
pointer: {
|
|
202
|
-
repository: input.repository,
|
|
203
|
-
prNumber: input.prNumber,
|
|
204
|
-
commentId: evidence.githubId,
|
|
205
|
-
...(evidence.htmlUrl === undefined ? {} : { htmlUrl: evidence.htmlUrl }),
|
|
206
|
-
...(evidence.authorLogin === undefined ? {} : { authorLogin: evidence.authorLogin }),
|
|
207
|
-
kind: evidence.kind,
|
|
208
|
-
authoritativeTime: evidence.authoritativeTime ?? null,
|
|
209
|
-
...(evidence.commitOid === undefined ? {} : { commitOid: evidence.commitOid }),
|
|
210
|
-
},
|
|
211
|
-
});
|
|
212
|
-
projected += 1;
|
|
213
|
-
}
|
|
214
|
-
return {
|
|
215
|
-
findingsSource: "array",
|
|
216
|
-
findingsProjectedCount: projected,
|
|
217
|
-
findingsUnprojected: unprojected,
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
/**
|
|
221
|
-
* #676 D6/C: optional unfinished reasons from the model submission.
|
|
222
|
-
* Project readable strings; record when original content could not fully project
|
|
223
|
-
* (do not wash unreadable unfinishedReasons into "none").
|
|
224
|
-
*/
|
|
225
|
-
export function extractCollectorUnfinishedReasons(candidate) {
|
|
226
|
-
if (candidate !== undefined && candidate !== null && candidateRecord(candidate) === undefined) {
|
|
227
|
-
return { reasons: undefined, source: "unreadable", unprojected: true };
|
|
228
|
-
}
|
|
229
|
-
const record = candidateRecord(candidate);
|
|
230
|
-
if (record === undefined) {
|
|
231
|
-
return { reasons: undefined, source: "absent", unprojected: false };
|
|
232
|
-
}
|
|
233
|
-
if (!Object.hasOwn(record, "unfinishedReasons")) {
|
|
234
|
-
// unfinishedReasons absent is fine when only findings (or nothing) present.
|
|
235
|
-
// Non-canonical keys alone are recorded on the findings projection path.
|
|
236
|
-
return { reasons: undefined, source: "absent", unprojected: false };
|
|
237
|
-
}
|
|
238
|
-
const raw = record["unfinishedReasons"];
|
|
239
|
-
if (!Array.isArray(raw)) {
|
|
240
|
-
return { reasons: undefined, source: "unreadable", unprojected: true };
|
|
241
|
-
}
|
|
242
|
-
const reasons = raw.filter((item) => typeof item === "string");
|
|
243
|
-
const unprojected = reasons.length !== raw.length;
|
|
244
|
-
return {
|
|
245
|
-
reasons: reasons.length > 0 ? reasons : undefined,
|
|
246
|
-
source: "array",
|
|
247
|
-
unprojected,
|
|
248
|
-
};
|
|
249
|
-
}
|
package/dist/collector-ledger.js
CHANGED
|
@@ -22,36 +22,9 @@ export const COLLECTOR_OPERATIONAL_TOOLS = [
|
|
|
22
22
|
COLLECTOR_WAIT_TOOL,
|
|
23
23
|
COLLECTOR_HANDBOOK_WRITE_TOOL,
|
|
24
24
|
];
|
|
25
|
-
/**
|
|
26
|
-
* #641 chain①: observe context projects at most this many bytes of each
|
|
27
|
-
* material body; the full body stays reachable via the record pointer
|
|
28
|
-
* (evidenceId + htmlUrl) and is preserved in the volume, never unconditionally
|
|
29
|
-
* transcribed into model context or receipt.
|
|
30
|
-
*/
|
|
31
|
-
const COLLECTOR_OBSERVE_BODY_HEAD_BYTES = 512;
|
|
32
|
-
function utf8SafePrefix(text, maxBytes) {
|
|
33
|
-
if (Buffer.byteLength(text, "utf8") <= maxBytes)
|
|
34
|
-
return text;
|
|
35
|
-
const bytes = Buffer.from(text, "utf8");
|
|
36
|
-
let end = maxBytes;
|
|
37
|
-
while (end > 0 && (bytes[end] & 0xc0) === 0x80)
|
|
38
|
-
end -= 1;
|
|
39
|
-
return `${bytes.subarray(0, end).toString("utf8")}…`;
|
|
40
|
-
}
|
|
41
|
-
/**
|
|
42
|
-
* #641 chain① model-context projection: identical shape to the volume view,
|
|
43
|
-
* with every material body bounded to a head preview. Full bodies remain
|
|
44
|
-
* pointer-reachable (evidenceId/htmlUrl) and stay in the ledger volume
|
|
45
|
-
* (collector-side; provider-visible observe details carry only this bounded
|
|
46
|
-
* projection), never unconditionally transcribed into model context.
|
|
47
|
-
*/
|
|
25
|
+
/** #836: observe context is the full volume view — no body truncation. */
|
|
48
26
|
function projectObserveContextView(modelView) {
|
|
49
|
-
return
|
|
50
|
-
...modelView,
|
|
51
|
-
evidence: modelView.evidence.map((entry) => entry.body === undefined
|
|
52
|
-
? entry
|
|
53
|
-
: { ...entry, body: utf8SafePrefix(entry.body, COLLECTOR_OBSERVE_BODY_HEAD_BYTES) }),
|
|
54
|
-
};
|
|
27
|
+
return modelView;
|
|
55
28
|
}
|
|
56
29
|
export const COLLECTOR_ACTIVATION_ENTRY_TYPE = "ak-collector-activation";
|
|
57
30
|
const COLLECTOR_SNAPSHOT_ENTRY_TYPE = "ak-collector-snapshot";
|
|
@@ -471,15 +444,6 @@ export function createCollectorLedger(input, options) {
|
|
|
471
444
|
},
|
|
472
445
|
bindTarget(prNumber) {
|
|
473
446
|
assertNotFatal();
|
|
474
|
-
if (outputCandidate || pendingOutputCallId !== undefined) {
|
|
475
|
-
throw new Error("通进司已产出输出候选,本局不再受理目标绑定");
|
|
476
|
-
}
|
|
477
|
-
if (!Number.isSafeInteger(prNumber) || prNumber < 1) {
|
|
478
|
-
throw new Error("通进司绑定目标要求正安全整数 PR 号");
|
|
479
|
-
}
|
|
480
|
-
if (config.prNumber !== undefined && config.prNumber !== prNumber) {
|
|
481
|
-
throw new Error(`通进司目标已绑定 PR ${config.prNumber},不可改绑为 ${prNumber}`);
|
|
482
|
-
}
|
|
483
447
|
config.prNumber = prNumber;
|
|
484
448
|
appendJournal("ak-collector-target-bound", {
|
|
485
449
|
prNumber,
|
|
@@ -488,10 +452,6 @@ export function createCollectorLedger(input, options) {
|
|
|
488
452
|
},
|
|
489
453
|
beginOperational(toolName, toolCallId) {
|
|
490
454
|
assertNotFatal();
|
|
491
|
-
if (toolName !== COLLECTOR_OUTPUT_TOOL &&
|
|
492
|
-
(outputCandidate || pendingOutputCallId !== undefined)) {
|
|
493
|
-
throw new Error("通进司已产出输出候选,本局不再受理操作");
|
|
494
|
-
}
|
|
495
455
|
if (toolName === COLLECTOR_OUTPUT_TOOL) {
|
|
496
456
|
pendingOutputCallId = toolCallId;
|
|
497
457
|
return;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Type } from "typebox";
|
|
2
2
|
import { auditorRunDirectory } from "./auditor-dossier-tool.js";
|
|
3
|
-
|
|
3
|
+
import { OFFICER_CONCLUSION_REASK } from "./gatekeeper-role.js";
|
|
4
|
+
const AUDITOR_DOSSIER_PROMPT = "\u5377\u5B97\u6307\u9488\uFF1A";
|
|
4
5
|
const nonblank = Type.String({ minLength: 1, pattern: "\\S" });
|
|
5
6
|
const decisionGateSchema = Type.Object({ question: nonblank, options: Type.Array(nonblank, { minItems: 1 }) }, { additionalProperties: false });
|
|
6
7
|
const complianceDecisionSchema = Type.Object({ status: Type.Unknown({ description: "pass | bounce | escalate \u2014 \u5F62\u72B6\u6307\u5F15\uFF0C\u975E schema \u95F8" }), violations: Type.Array(nonblank, { description: "\u89C2\u5BDF\u5230\u7684\u5408\u89C4\u8FDD\u89C4" }), conflicts: Type.Array(nonblank, { description: "\u672A\u51B3\u6743\u5A01\u6216\u6267\u884C\u51B2\u7A81" }), decisionGate: Type.Union([decisionGateSchema, Type.Null()], { description: "\u5347\u7EA7\u95EE\u9898\u4E0E\u53EF\u9009\u9009\u9879" }) }, { additionalProperties: true, required: [] });
|
|
@@ -22,11 +23,12 @@ function tryReadComplianceCandidate(arguments_, usage) {
|
|
|
22
23
|
}
|
|
23
24
|
const args = arguments_;
|
|
24
25
|
const status = args.status;
|
|
25
|
-
if (status === "pass") return { status, ...usage === void 0 ? {} : { usage } };
|
|
26
|
-
if (status === "bounce") return { status, violations: readListField(args.violations), ...usage === void 0 ? {} : { usage } };
|
|
26
|
+
if (status === "pass") return { status, receipt: arguments_, ...usage === void 0 ? {} : { usage } };
|
|
27
|
+
if (status === "bounce") return { status, violations: readListField(args.violations), receipt: arguments_, ...usage === void 0 ? {} : { usage } };
|
|
27
28
|
if (status === "escalate") {
|
|
28
29
|
return {
|
|
29
30
|
status,
|
|
31
|
+
receipt: arguments_,
|
|
30
32
|
...Object.hasOwn(args, "conflicts") ? { conflicts: args.conflicts } : {},
|
|
31
33
|
...Object.hasOwn(args, "decisionGate") ? { decisionGate: args.decisionGate } : {},
|
|
32
34
|
...usage === void 0 ? {} : { usage }
|
|
@@ -65,14 +67,29 @@ async function projectAuditorTerminal(summoned) {
|
|
|
65
67
|
};
|
|
66
68
|
}
|
|
67
69
|
if (outcome.kind === "failure") {
|
|
68
|
-
|
|
70
|
+
const rows = outcome.payloads ?? summoned.terminal?.submissions ?? [];
|
|
71
|
+
return {
|
|
72
|
+
status: "transport_failure",
|
|
73
|
+
diagnostic: outcome.diagnostic,
|
|
74
|
+
...rows.length > 0 ? { submissions: rows } : {},
|
|
75
|
+
...summoned.terminal === void 0 ? {} : { terminal: summoned.terminal },
|
|
76
|
+
...usage === void 0 ? {} : { usage }
|
|
77
|
+
};
|
|
69
78
|
}
|
|
70
79
|
if (outcome.kind === "accepted") {
|
|
71
|
-
const
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
|
|
80
|
+
const rows = outcome.payloads ?? summoned.terminal?.submissions ?? [];
|
|
81
|
+
if (rows.length === 0) {
|
|
82
|
+
return readComplianceCandidate({}, usage);
|
|
83
|
+
}
|
|
84
|
+
const decision = readComplianceCandidate(rows[rows.length - 1], usage);
|
|
85
|
+
if (rows.length === 1) return decision;
|
|
86
|
+
if (decision.status === "pass" || decision.status === "bounce" || decision.status === "escalate") {
|
|
87
|
+
return { ...decision, receipt: rows };
|
|
88
|
+
}
|
|
89
|
+
if (decision.status === "received") {
|
|
90
|
+
return { ...decision, reply: rows };
|
|
91
|
+
}
|
|
92
|
+
return decision;
|
|
76
93
|
}
|
|
77
94
|
return {
|
|
78
95
|
status: "received",
|
|
@@ -86,7 +103,7 @@ async function runComplianceAudit(options) {
|
|
|
86
103
|
throw new Error("Compliance audit requires a parent run directory pointer");
|
|
87
104
|
}
|
|
88
105
|
const subject = options.subject;
|
|
89
|
-
const summon = options.summonAuditor ?? (async (auditSubject, sourceRunDirectory, auditSignal) => {
|
|
106
|
+
const summon = options.summonAuditor ?? (async (auditSubject, sourceRunDirectory, auditSignal, reask2) => {
|
|
90
107
|
const { summonPublicRole } = await import("./public-role-summons.js");
|
|
91
108
|
const { homeFromRunDirectory } = await import("./activation-ledger-topology.js");
|
|
92
109
|
const home = homeFromRunDirectory(sourceRunDirectory);
|
|
@@ -97,15 +114,24 @@ async function runComplianceAudit(options) {
|
|
|
97
114
|
auditSubject,
|
|
98
115
|
"--source-run",
|
|
99
116
|
sourceRunDirectory,
|
|
100
|
-
AUDITOR_DOSSIER_PROMPT
|
|
117
|
+
`${AUDITOR_DOSSIER_PROMPT}${sourceRunDirectory}`
|
|
101
118
|
],
|
|
102
119
|
cwd: options.context.cwd ?? process.cwd(),
|
|
103
120
|
home,
|
|
104
|
-
...auditSignal === void 0 ? {} : { signal: auditSignal }
|
|
121
|
+
...auditSignal === void 0 ? {} : { signal: auditSignal },
|
|
122
|
+
...reask2 === void 0 ? {} : { reviewReask: reask2 }
|
|
105
123
|
});
|
|
106
124
|
});
|
|
107
|
-
|
|
108
|
-
|
|
125
|
+
let reask;
|
|
126
|
+
for (; ; ) {
|
|
127
|
+
const summoned = await summon(subject, runDirectory, options.signal, reask);
|
|
128
|
+
const decision = await projectAuditorTerminal(summoned);
|
|
129
|
+
if (decision.status === "received") {
|
|
130
|
+
reask = OFFICER_CONCLUSION_REASK;
|
|
131
|
+
continue;
|
|
132
|
+
}
|
|
133
|
+
return decision;
|
|
134
|
+
}
|
|
109
135
|
}
|
|
110
136
|
export {
|
|
111
137
|
AUDITOR_COMPLIANCE_FAILURE_ENTRY_TYPE,
|
|
@@ -25,46 +25,8 @@ export function validateRecordedDiaristOutput(value) {
|
|
|
25
25
|
}
|
|
26
26
|
throw new Error("Diarist output has no execution discriminator");
|
|
27
27
|
}
|
|
28
|
-
/**
|
|
29
|
-
* Lenient projection of submitted entries (第 0 条: shape is not an admission
|
|
30
|
-
* gate). Rows that cannot form a lawful volume entry are dropped from the
|
|
31
|
-
* commit, never bounced back at the role. This is write-seam self-check only —
|
|
32
|
-
* not quote/ticket/relevance judgment of LLM content (#779).
|
|
33
|
-
*/
|
|
28
|
+
/** Entries array as the diarist wrote it — no field rewrite (#836 B6.8/B6.9). */
|
|
34
29
|
export function projectDiaristEntries(value) {
|
|
35
30
|
const rows = value?.entries;
|
|
36
|
-
|
|
37
|
-
return [];
|
|
38
|
-
const out = [];
|
|
39
|
-
for (const row of rows) {
|
|
40
|
-
if (row === null || typeof row !== "object" || Array.isArray(row))
|
|
41
|
-
continue;
|
|
42
|
-
const record = row;
|
|
43
|
-
if (typeof record.sourceKind !== "string")
|
|
44
|
-
continue;
|
|
45
|
-
if (typeof record.transcript !== "string" || record.transcript.length === 0)
|
|
46
|
-
continue;
|
|
47
|
-
if (typeof record.timestamp !== "string" || record.timestamp.length === 0)
|
|
48
|
-
continue;
|
|
49
|
-
if (record.sourceRef === null || typeof record.sourceRef !== "object" || Array.isArray(record.sourceRef)) {
|
|
50
|
-
continue;
|
|
51
|
-
}
|
|
52
|
-
const ref = record.sourceRef;
|
|
53
|
-
const sourceRef = {
|
|
54
|
-
...(typeof ref.sessionFile === "string" ? { sessionFile: ref.sessionFile } : {}),
|
|
55
|
-
...(typeof ref.entryId === "string" || typeof ref.entryId === "number"
|
|
56
|
-
? { entryId: ref.entryId }
|
|
57
|
-
: {}),
|
|
58
|
-
...(typeof ref.path === "string" ? { path: ref.path } : {}),
|
|
59
|
-
...(typeof ref.url === "string" ? { url: ref.url } : {}),
|
|
60
|
-
};
|
|
61
|
-
out.push({
|
|
62
|
-
sourceKind: record.sourceKind,
|
|
63
|
-
sourceRef,
|
|
64
|
-
transcript: record.transcript,
|
|
65
|
-
timestamp: record.timestamp,
|
|
66
|
-
...(typeof record.note === "string" ? { note: record.note } : {}),
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
return out;
|
|
31
|
+
return Array.isArray(rows) ? [...rows] : [];
|
|
70
32
|
}
|