@akagilnc/pi-workflow-roles 0.1.5368 → 0.1.5382
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/dist/acp-host/production-host.js +1192 -1195
- package/dist/activation-ledger-git.js +2 -2
- package/dist/activation-ledger-topology.js +1 -7
- package/dist/analyst-entry.js +4 -8
- package/dist/analyst-gate-cycles-read.js +3 -11
- package/dist/analyst-index.js +3 -9
- package/dist/analyst-ledger.js +1 -11
- package/dist/analyst-metric-families/acceptance-success-rework.js +1 -3
- package/dist/analyst-metric-families/round-timeline.js +1 -3
- package/dist/analyst-page.js +2 -2
- package/dist/archivist-record-topology.js +2 -2
- package/dist/audit-escalation.js +2 -1
- package/dist/auditor-soul.js +2 -1
- package/dist/collector-config.js +6 -8
- package/dist/collector-tool-schemas.js +18 -0
- package/dist/compliance-transport.js +2 -1
- package/dist/countersign-contracts.js +2 -1
- package/dist/diarist-contracts.js +29 -2
- package/dist/doctor-contracts.js +1 -3
- package/dist/doctor-evidence.js +7 -8
- package/dist/dossier-resolution.js +1 -3
- package/dist/engine-detour-usage.js +1 -3
- package/dist/filed-submission.js +28 -0
- package/dist/gatekeeper-role.js +8 -19
- package/dist/gleaner-left-contracts.js +18 -1
- package/dist/headless-host/description.js +14 -17
- package/dist/headless-host/production-host.js +1210 -1215
- package/dist/host-session-record.js +3 -2
- package/dist/inspector-contracts.js +2 -1
- package/dist/judge-role.js +8 -16
- package/dist/ledger-session-read.js +1 -3
- package/dist/merger-contracts.js +9 -9
- package/dist/migrate-book-topology.js +610 -321
- package/dist/navigator-attendance.js +9 -11
- package/dist/navigator-invocation-identity.js +3 -2
- package/dist/navigator-public-session.js +3 -5
- package/dist/navigator-session-contracts.js +8 -10
- package/dist/navigator-work-base.js +5 -8
- package/dist/notary-contracts.js +3 -5
- package/dist/package-contracts/auditor-output.js +1 -3
- package/dist/package-contracts/fixer-packet.js +3 -6
- package/dist/package-contracts/gatekeeper-output.js +1 -3
- package/dist/package-contracts/navigator-output.js +8 -8
- package/dist/package-contracts/reviewer-output.js +16 -0
- package/dist/package-contracts/terminating-infrastructure.js +5 -9
- package/dist/package-contracts/terminating-tools.js +4 -3
- package/dist/package-contracts/worker-output.js +18 -2
- package/dist/public-cli/analyst-run.js +3 -2
- package/dist/public-cli/auto-resume.js +3 -8
- package/dist/public-cli/cli.js +9 -8
- package/dist/public-cli/config.js +8 -11
- package/dist/public-cli/countersign-run.js +1 -3
- package/dist/public-cli/host-providers.js +7 -14
- package/dist/public-cli/instruction-seat-run.js +12 -14
- package/dist/public-cli/invocation.js +9 -8
- package/dist/public-cli/main.js +1820 -1599
- package/dist/public-cli/post-admission.js +4 -3
- package/dist/public-cli/run-lifecycle.js +14 -26
- package/dist/public-cli/settlement.js +10 -26
- package/dist/public-role-summons.js +3 -2
- package/dist/receipt-delivery-policy.js +1 -3
- package/dist/role-run-placement.js +3 -5
- package/dist/role-run-relocation.js +12 -19
- package/dist/role-submission-declarations.js +131 -0
- package/dist/run-terminal-artifacts.js +1 -11
- package/dist/run-ticket-number.js +2 -4
- package/dist/secretariat-contracts.js +23 -1
- package/dist/session-assistant-usage.js +2 -1
- package/dist/session-dialogue.js +1 -14
- package/dist/sitian-reader.js +2 -4
- package/dist/submission-correctable-error.js +2 -1
- package/dist/submission-errors.js +2 -1
- package/dist/submission-gate.js +6 -8
- package/dist/submission-ledger.js +4 -5
- package/dist/ticket-provenance-contracts.js +1 -3
- package/dist/ticket-provenance.js +2 -1
- package/dist/typed-provider-http.js +4 -7
- package/dist/unknown-value.js +28 -0
- package/dist/user-dialogue-stdin.js +2 -1
- package/package.json +1 -1
- package/src/acp-host/role-turn-host.ts +4 -2
- package/src/activation-ledger-git.ts +3 -2
- package/src/activation-ledger-topology.ts +4 -12
- package/src/analyst-entry.ts +3 -11
- package/src/analyst-gate-cycles-read.ts +3 -15
- package/src/analyst-index.ts +3 -13
- package/src/analyst-ledger.ts +2 -16
- package/src/analyst-metric-families/acceptance-success-rework.ts +2 -4
- package/src/analyst-metric-families/round-timeline.ts +2 -4
- package/src/analyst-page.ts +2 -2
- package/src/archivist-record-topology.ts +2 -2
- package/src/audit-escalation.ts +2 -1
- package/src/auditor-role.ts +2 -8
- package/src/auditor-soul.ts +3 -1
- package/src/book-topology-migration-placement.ts +5 -16
- package/src/book-topology-migrators-t11.ts +16 -15
- package/src/book-topology-mixed-volume-migrators.ts +3 -6
- package/src/book-topology-record-class-migrators.ts +7 -10
- package/src/book-topology-runs-migrator.ts +4 -3
- package/src/collector-config.ts +7 -9
- package/src/collector-role.ts +5 -28
- package/src/compliance-transport.ts +2 -1
- package/src/countersign-contracts.ts +2 -1
- package/src/countersign-role.ts +2 -7
- package/src/diarist-contracts.ts +40 -2
- package/src/diarist-role.ts +4 -44
- package/src/doctor-contracts.ts +3 -1
- package/src/doctor-evidence.ts +8 -7
- package/src/doctor-role.ts +20 -13
- package/src/dossier-resolution.ts +2 -4
- package/src/engine-detour-tool.ts +0 -2
- package/src/engine-detour-usage.ts +2 -4
- package/src/external-host-turn-loop.ts +3 -1
- package/src/factory-board.ts +1 -8
- package/src/filed-submission.ts +44 -0
- package/src/gatekeeper-role.ts +8 -24
- package/src/gleaner-left-contracts.ts +29 -2
- package/src/gleaner-left-role.ts +4 -32
- package/src/headless-host/description.ts +15 -18
- package/src/headless-host/role-turn-host.ts +10 -9
- package/src/host-session-record.ts +4 -2
- package/src/inspector-contracts.ts +2 -1
- package/src/inspector-role.ts +2 -7
- package/src/judge-role.ts +12 -24
- package/src/ledger-session-read.ts +2 -5
- package/src/merger-contracts.ts +10 -9
- package/src/merger-role.ts +5 -8
- package/src/navigator-attendance.ts +10 -12
- package/src/navigator-invocation-identity.ts +3 -2
- package/src/navigator-public-session.ts +4 -6
- package/src/navigator-role.ts +2 -8
- package/src/navigator-session-contracts.ts +8 -11
- package/src/navigator-work-base.ts +5 -8
- package/src/navigator-work-context.ts +2 -1
- package/src/notary-contracts.ts +4 -6
- package/src/notary-role.ts +5 -20
- package/src/oauth-keepalive.ts +5 -18
- package/src/package-contracts/auditor-output.ts +2 -4
- package/src/package-contracts/fixer-packet.ts +4 -6
- package/src/package-contracts/gatekeeper-output.ts +2 -4
- package/src/package-contracts/judge-output.ts +0 -1
- package/src/package-contracts/navigator-output.ts +8 -8
- package/src/package-contracts/reviewer-output.ts +25 -0
- package/src/package-contracts/terminating-infrastructure.ts +6 -20
- package/src/package-contracts/terminating-tools.ts +8 -12
- package/src/package-contracts/worker-output.ts +43 -5
- package/src/pi/adapter.ts +0 -1
- package/src/pi/role-turn-host.ts +0 -1
- package/src/public-cli/analyst-run.ts +4 -2
- package/src/public-cli/auto-resume.ts +4 -11
- package/src/public-cli/cli.ts +10 -10
- package/src/public-cli/config.ts +8 -27
- package/src/public-cli/countersign-run.ts +2 -4
- package/src/public-cli/host-providers.ts +10 -27
- package/src/public-cli/instruction-seat-run.ts +14 -15
- package/src/public-cli/invocation.ts +11 -10
- package/src/public-cli/post-admission.ts +5 -3
- package/src/public-cli/run-lifecycle.ts +20 -41
- package/src/public-cli/settlement.ts +11 -33
- package/src/public-role-summons.ts +4 -3
- package/src/receipt-delivery-policy.ts +1 -4
- package/src/reviewer-role.ts +7 -36
- package/src/role-envelope.ts +5 -3
- package/src/role-run-placement.ts +4 -10
- package/src/role-run-relocation.ts +13 -23
- package/src/role-runtime.ts +16 -50
- package/src/role-submission-declarations.ts +143 -0
- package/src/run-terminal-artifacts.ts +2 -16
- package/src/run-ticket-number.ts +4 -10
- package/src/secretariat-contracts.ts +35 -0
- package/src/secretariat-role.ts +4 -40
- package/src/session-assistant-usage.ts +3 -1
- package/src/session-dialogue.ts +1 -4
- package/src/session-identity.ts +0 -1
- package/src/sitian-reader.ts +2 -4
- package/src/submission-correctable-error.ts +3 -1
- package/src/submission-errors.ts +2 -1
- package/src/submission-gate.ts +10 -9
- package/src/submission-ledger.ts +5 -6
- package/src/ticket-provenance-contracts.ts +1 -4
- package/src/ticket-provenance.ts +3 -1
- package/src/ticket-snapshot.ts +2 -4
- package/src/ticket-trajectory.ts +1 -8
- package/src/typed-provider-http.ts +5 -15
- package/src/unknown-value.ts +34 -0
- package/src/user-dialogue-stdin.ts +3 -1
- package/src/worker-role.ts +63 -83
- package/src/worker-submission-gates.ts +8 -7
package/src/doctor-contracts.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { canonicalJson } from "./canonical-json.ts";
|
|
|
3
3
|
import { openToolObject } from "./open-tool-schema.ts";
|
|
4
4
|
import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
|
|
5
5
|
|
|
6
|
+
import { isRecord } from "./unknown-value.ts";
|
|
7
|
+
|
|
6
8
|
export const DOCTOR_EVIDENCE_TOOL_NAME = "ak_doctor_evidence";
|
|
7
9
|
export const DOCTOR_OUTPUT_TOOL_NAME = "ak_doctor_output";
|
|
8
10
|
export const DOCTOR_AUDIT_TOOL_NAME = "ak_doctor_audit_decision";
|
|
@@ -85,7 +87,7 @@ export const doctorSubmissionSchema = withTerminatingOutputDeclarations(
|
|
|
85
87
|
// consumes extra fields, so a closed object only rejects the role for saying more.
|
|
86
88
|
export const doctorEvidenceReadSchema = Type.Object({ evidenceId: Type.String({ minLength: 1, description: "待读留存证据标识" }), offset: Type.Optional(Type.Integer({ minimum: 0, description: "起始字节偏移(从 0 计)" })), limit: Type.Optional(Type.Integer({ minimum: 1, description: "返回字节数(无上限)" })) }, { additionalProperties: true });
|
|
87
89
|
export class DoctorSubmissionContractError extends Error { override readonly name = "DoctorSubmissionContractError"; }
|
|
88
|
-
|
|
90
|
+
|
|
89
91
|
function read(value: unknown, key: string): unknown { if (!isRecord(value)) return undefined; try { return value[key]; } catch { return undefined; } }
|
|
90
92
|
export function validateDoctorSubmissionShape(value: unknown): DoctorSubmission {
|
|
91
93
|
return value as DoctorSubmission;
|
package/src/doctor-evidence.ts
CHANGED
|
@@ -4,26 +4,27 @@ import { sha256Hex } from "./sha256.ts";
|
|
|
4
4
|
import type { DoctorCase, DoctorCaseCost, DoctorCount, DoctorEvidenceEntry } from "./doctor-contracts.ts";
|
|
5
5
|
import { AcceptedDetailsContractError, acceptedFacts, isTerminatingToolName, validateAcceptedDetails } from "./package-contracts/terminating-tools.ts";
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
import { isRecord, isMissingPathError } from "./unknown-value.ts";
|
|
8
|
+
|
|
8
9
|
async function discoverCaseFiles(root: string): Promise<string[]> { const found: string[] = []; async function walk(dir: string, depth: number) { for (const item of await readdir(dir, { withFileTypes: true })) { const path = resolve(dir, item.name); if (item.isDirectory()) { await walk(path, depth + 1); } else if (item.isFile() && (item.name.endsWith(".jsonl") || (item.name === "stderr.log" && depth === 1))) found.push(path); } } await walk(root, 0); return found.sort(); }
|
|
9
10
|
function sourceList(count: number, sources: string[]) { return { count, sources: [...new Set(sources)].sort() }; }
|
|
10
11
|
function accumulate(metric: DoctorCount, value: number, source: string) { metric.count += value; if (value) metric.sources.push(source); }
|
|
11
12
|
function timestamp(row: Record<string, unknown>) { return typeof row.timestamp === "string" && Number.isFinite(Date.parse(row.timestamp)) ? row.timestamp : undefined; }
|
|
12
|
-
|
|
13
|
+
|
|
13
14
|
async function stableRunsIdentity(root: string): Promise<string> { let cursor = root; while (true) { try { const git = await stat(resolve(cursor, ".git")); if (git.isDirectory() || git.isFile()) return relative(cursor, root).split(sep).join("/"); } catch (error) { if (!isMissingPathError(error)) throw error; } const parent = dirname(cursor); if (parent === cursor) return root; cursor = parent; } }
|
|
14
15
|
|
|
15
16
|
type SessionDerivation = { session: DoctorCaseCost["sessions"][number]; turns: number; calls: number; tokens: number; statuses: DoctorCaseCost["statuses"]; commits: DoctorCaseCost["commits"] };
|
|
16
17
|
function deriveSession(content: string, id: string): SessionDerivation {
|
|
17
18
|
const rows: Record<string, unknown>[] = []; const degradationReasons: string[] = [];
|
|
18
|
-
for (const line of content.split("\n")) if (line.trim()) { try { const row: unknown = JSON.parse(line); if (!
|
|
19
|
+
for (const line of content.split("\n")) if (line.trim()) { try { const row: unknown = JSON.parse(line); if (!isRecord(row)) { degradationReasons.push(`non-object session row in ${id}`); break; } rows.push(row); } catch (error) { if (error instanceof SyntaxError) { degradationReasons.push(`malformed JSON tail in ${id}: ${error.message}`); break; } throw error; } }
|
|
19
20
|
const started = rows.find((row) => row.type === "session"); const startedAt = started && timestamp(started); if (!startedAt) degradationReasons.push(`Pi session header is missing: ${id}`);
|
|
20
21
|
let accepted: Record<string, unknown> | undefined, observedCommit: string | undefined, turns = 0, calls = 0, tokens = 0;
|
|
21
22
|
const statuses: DoctorCaseCost["statuses"] = [], commits: DoctorCaseCost["commits"] = [];
|
|
22
23
|
for (const row of rows) {
|
|
23
|
-
if (row.type === "custom" && row.customType === "ak-role-submission-closure" &&
|
|
24
|
-
const message =
|
|
25
|
-
if (message?.role === "assistant") { for (const part of Array.isArray(message.content) ? message.content : []) if (
|
|
26
|
-
if (message?.role === "toolResult" && message.isError !== true && typeof message.toolName === "string" && isTerminatingToolName(message.toolName) &&
|
|
24
|
+
if (row.type === "custom" && row.customType === "ak-role-submission-closure" && isRecord(row.data) && row.data.isError === false && typeof row.data.toolName === "string" && isTerminatingToolName(row.data.toolName)) accepted = row;
|
|
25
|
+
const message = isRecord(row.message) ? row.message : undefined;
|
|
26
|
+
if (message?.role === "assistant") { for (const part of Array.isArray(message.content) ? message.content : []) if (isRecord(part) && part.type === "toolCall") calls++; if (typeof message.responseId === "string") { turns++; const usage = isRecord(message.usage) ? message.usage : undefined; if (usage && typeof usage.output === "number") tokens += usage.output; } }
|
|
27
|
+
if (message?.role === "toolResult" && message.isError !== true && typeof message.toolName === "string" && isTerminatingToolName(message.toolName) && isRecord(message.details)) { let details; try { details = validateAcceptedDetails(message.toolName, message.details); } catch (error) { // Contract: README.md#Doctor — non-accepted terminating receipts are expected-negative evidence and are skipped; all other validation failures propagate with their cause.
|
|
27
28
|
if (error instanceof AcceptedDetailsContractError) continue; throw error; } const facts = acceptedFacts(message.toolName, details); if (facts.commit && facts.commit !== observedCommit) { commits.push({ source: id, commit: facts.commit }); observedCommit = facts.commit; } /* #836 B10.3: do not wipe earlier statuses or invent a code status sentence */ if (facts.status !== undefined) { statuses.push({ source: id, status: facts.status }); } }
|
|
28
29
|
}
|
|
29
30
|
const acceptedAt = accepted && timestamp(accepted); const final = acceptedAt ? accepted! : rows.at(-1); const endedAt = final && timestamp(final); const wall = startedAt && endedAt ? Date.parse(endedAt) - Date.parse(startedAt) : undefined;
|
package/src/doctor-role.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
|
-
import type { RoleHost, HostContext
|
|
1
|
+
import type { RoleHost, HostContext } from "./host-contracts.ts";
|
|
2
|
+
import { registerFiledSubmissionTool } from "./filed-submission.ts";
|
|
3
|
+
import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
|
|
2
4
|
import { ComplianceResponseRetentionError } from "./compliance-transport.ts";
|
|
3
5
|
import { DOCTOR_CANDIDATE_ENTRY_TYPE } from "./dossier-resolution.ts";
|
|
4
|
-
import { DOCTOR_EVIDENCE_TOOL_NAME,
|
|
6
|
+
import { DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_NAME, DoctorEvidenceStore, doctorEvidenceReadSchema, validateDoctorOutput, type DoctorCase } from "./doctor-contracts.ts";
|
|
5
7
|
|
|
6
8
|
import { sitianReport } from "./sitian-facade.ts";
|
|
7
9
|
|
|
10
|
+
import { errorText } from "./unknown-value.ts";
|
|
11
|
+
|
|
8
12
|
export { DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_NAME };
|
|
9
13
|
export const DOCTOR_CASE_FLAG = { name: "ak-doctor-case", definition: { description: "Retained .ak-roles/books/<book>/<n>/runs directory", type: "string" as const } } as const;
|
|
10
14
|
export type DoctorRoleDependencies = { loadSoul(): Promise<string>; loadCase(path: string): Promise<DoctorCase> };
|
|
@@ -19,14 +23,14 @@ function appendCandidate(ctx: HostContext, data: unknown): void {
|
|
|
19
23
|
source: "doctor-role",
|
|
20
24
|
});
|
|
21
25
|
} catch (error) {
|
|
22
|
-
throw new ComplianceResponseRetentionError(`太医署候选留存失败: ${
|
|
26
|
+
throw new ComplianceResponseRetentionError(`太医署候选留存失败: ${errorText(error)}`, { cause: error });
|
|
23
27
|
}
|
|
24
28
|
const append = ctx.sessionManager.appendCustomEntry;
|
|
25
29
|
if (typeof append === "function") {
|
|
26
30
|
try {
|
|
27
31
|
append.call(ctx.sessionManager, DOCTOR_CANDIDATE_ENTRY_TYPE, data);
|
|
28
32
|
} catch (error) {
|
|
29
|
-
throw new ComplianceResponseRetentionError(`太医署候选留存失败: ${
|
|
33
|
+
throw new ComplianceResponseRetentionError(`太医署候选留存失败: ${errorText(error)}`, { cause: error });
|
|
30
34
|
}
|
|
31
35
|
}
|
|
32
36
|
}
|
|
@@ -34,15 +38,18 @@ export function createDoctorRoleRuntime(pi: RoleHost, dependencies: DoctorRoleDe
|
|
|
34
38
|
let activation: { soul: string; patient: DoctorCase; store: DoctorEvidenceStore } | undefined; let registered = false; pi.registerFlag(DOCTOR_CASE_FLAG.name, DOCTOR_CASE_FLAG.definition);
|
|
35
39
|
return { async activate() { const path = pi.getFlag(DOCTOR_CASE_FLAG.name); if (typeof path !== "string" || !path.trim()) throw new Error("Doctor requires --ak-doctor-case"); const soul = (await dependencies.loadSoul()).trim(); if (!soul) throw new Error("Doctor soul is empty"); const patient = await dependencies.loadCase(path); activation = { soul, patient, store: new DoctorEvidenceStore(patient) };
|
|
36
40
|
if (!registered) { registered = true; pi.registerTool({ name: DOCTOR_EVIDENCE_TOOL_NAME, label: "太医署证据", description: "分页读取留存的 Pi session 字节。", parameters: doctorEvidenceReadSchema, async execute(_id: string, params: { evidenceId: string; offset?: number; limit?: number }) { if (!activation) throw new Error("太医署未激活"); const details = activation.store.read(params.evidenceId, params.offset, params.limit); return { content: [{ type: "text" as const, text: JSON.stringify(details) }], details }; } });
|
|
37
|
-
pi
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
41
|
+
registerFiledSubmissionTool(pi, roleSubmissionDeclaration("doctor"), {
|
|
42
|
+
readyError: () => (activation === undefined ? "太医署未激活" : undefined),
|
|
43
|
+
beforeAccept: async ({ toolCallId, parameters, ctx }) => {
|
|
44
|
+
if (activation === undefined) throw new Error("太医署未激活");
|
|
45
|
+
const active = activation;
|
|
46
|
+
const testimony = validateDoctorOutput(parameters, active.patient, active.store);
|
|
47
|
+
try {
|
|
48
|
+
appendCandidate(ctx, { version: 1, testimony, cost: active.patient.cost, readRecord: active.store.readRecord(), patientIdentity: active.patient.identity });
|
|
49
|
+
} catch (error) { host.failInfrastructure(error, ctx, toolCallId); }
|
|
50
|
+
return testimony;
|
|
51
|
+
},
|
|
52
|
+
});
|
|
46
53
|
pi.on("before_agent_start", (event) => { if (!activation) throw new Error("太医署未激活"); const catalog = { version: activation.patient.version, identity: activation.patient.identity, admittedMetrics: { cost: activation.patient.cost }, lawfulTargetKeys: ["case", ...activation.patient.cost.invocations.sources], evidence: activation.patient.evidence.map(({ id, kind, sha256, byteLength, contentLength }) => ({ id, kind, sha256, byteLength, contentLength })) }; return { systemPrompt: `${event.systemPrompt}\n\n<doctor_soul>\n${activation.soul}\n</doctor_soul>\n\n<doctor_case>\n${JSON.stringify(catalog)}\n</doctor_case>` }; }); }
|
|
47
54
|
const required = [DOCTOR_EVIDENCE_TOOL_NAME, DOCTOR_OUTPUT_TOOL_NAME]; const names = pi.getAllTools().map((tool) => tool.name); for (const name of required) if (names.filter((item) => item === name).length !== 1) throw new Error(`Doctor required tool collision or missing: ${name}`); pi.setActiveTools(required); const active = pi.getActiveTools?.() ?? required; if (active.length !== 2 || !required.every((name) => active.includes(name))) throw new Error("Doctor active tool narrowing failed"); } };
|
|
48
55
|
}
|
|
@@ -9,6 +9,8 @@ import { resolve } from "node:path";
|
|
|
9
9
|
|
|
10
10
|
import { runDirectoryFromHostContext, type HostContext } from "./host-contracts.ts";
|
|
11
11
|
|
|
12
|
+
import { isRecord } from "./unknown-value.ts";
|
|
13
|
+
|
|
12
14
|
export const AUDIT_RUN_DIR_ENV = "AK_ROLE_RUN_DIR" as const;
|
|
13
15
|
export const DOCTOR_CANDIDATE_ENTRY_TYPE = "ak_doctor_audit_candidate" as const;
|
|
14
16
|
|
|
@@ -77,10 +79,6 @@ export function resolveAuditDossier(
|
|
|
77
79
|
return { status: "ok", runDirectory };
|
|
78
80
|
}
|
|
79
81
|
|
|
80
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
81
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
82
|
type AuditSubjectContext = { sessionManager: { getEntries?(): Iterable<unknown> } };
|
|
85
83
|
|
|
86
84
|
/**
|
|
@@ -22,8 +22,6 @@ import {
|
|
|
22
22
|
} from "./engine-detour-usage.ts";
|
|
23
23
|
import { runIdFromRunDirectory } from "./run-terminal-artifacts.ts";
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
25
|
// #836 r16 class 3: argv required/minItems/element-minLength stay — execute()
|
|
28
26
|
// must obtain the first item as the executable and spawn it (below; #82-98).
|
|
29
27
|
// Root additionalProperties:false is deleted — execute() reads only `argv`.
|
|
@@ -27,6 +27,8 @@ import {
|
|
|
27
27
|
type RecordPointer,
|
|
28
28
|
} from "./sitian-facade.ts";
|
|
29
29
|
|
|
30
|
+
import { isRecord } from "./unknown-value.ts";
|
|
31
|
+
|
|
30
32
|
/** Sitian event kind for one detour-tool call (volume under session/). */
|
|
31
33
|
export const ENGINE_DETOUR_CALL_KIND = "engine-detour-call" as const;
|
|
32
34
|
|
|
@@ -155,10 +157,6 @@ export function reportEngineDetourCall(
|
|
|
155
157
|
};
|
|
156
158
|
}
|
|
157
159
|
|
|
158
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
159
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
160
|
function callFactFromSitianPayload(
|
|
163
161
|
payload: unknown,
|
|
164
162
|
pointer: RecordPointer,
|
|
@@ -11,6 +11,8 @@ import type {
|
|
|
11
11
|
} from "./host-contracts.ts";
|
|
12
12
|
import type { PreparedRoleTurn } from "./prepared-role-turn.ts";
|
|
13
13
|
|
|
14
|
+
import { errorText } from "./unknown-value.ts";
|
|
15
|
+
|
|
14
16
|
export const EXTERNAL_ROLE_TURN_ROUND_LIMIT = 8 as const;
|
|
15
17
|
|
|
16
18
|
export type ExternalPreparedTurn = Pick<PreparedRoleTurn, "prompt" | "abortSignal" | "closeRound">;
|
|
@@ -103,7 +105,7 @@ export function withExternalHostCleanupFailure(
|
|
|
103
105
|
cleanupError: unknown,
|
|
104
106
|
name: string,
|
|
105
107
|
): RoleTurnResult {
|
|
106
|
-
const message =
|
|
108
|
+
const message = errorText(cleanupError);
|
|
107
109
|
if (outcome.knownFailure === undefined) {
|
|
108
110
|
return externalHostFailure("session", name, "dispose-failed", { cleanupError: message }, message);
|
|
109
111
|
}
|
package/src/factory-board.ts
CHANGED
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
} from "./ticket-trajectory.ts";
|
|
42
42
|
import type { BoardSnapshot, SnapshotTicket, TicketIssueState } from "./ticket-snapshot.ts";
|
|
43
43
|
|
|
44
|
+
import { isRecord, isMissingPathError } from "./unknown-value.ts";
|
|
44
45
|
|
|
45
46
|
/** Unaccepted latest-run mtime bands (page-visible thresholds). */
|
|
46
47
|
export const UNACCEPTED_FLYING_MS = 2 * 60 * 1000;
|
|
@@ -308,14 +309,6 @@ function currentStateLabel(state: TicketCurrentState): string {
|
|
|
308
309
|
}
|
|
309
310
|
}
|
|
310
311
|
|
|
311
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
312
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function isMissingPathError(error: unknown): boolean {
|
|
316
|
-
return error instanceof Error && "code" in error && (error.code === "ENOENT" || error.code === "ENOTDIR");
|
|
317
|
-
}
|
|
318
|
-
|
|
319
312
|
function escapeHtml(text: string): string {
|
|
320
313
|
return text
|
|
321
314
|
.replaceAll("&", "&")
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { HostContext, HostToolResult, RoleHost } from "./host-contracts.ts";
|
|
2
|
+
import type { RoleSubmissionDeclaration } from "./role-submission-declarations.ts";
|
|
3
|
+
|
|
4
|
+
export type FiledSubmissionBeforeAccept = (input: {
|
|
5
|
+
readonly toolCallId: string;
|
|
6
|
+
readonly parameters: unknown;
|
|
7
|
+
readonly signal: AbortSignal | undefined;
|
|
8
|
+
readonly ctx: HostContext;
|
|
9
|
+
}) => Promise<unknown>;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* One host-neutral submission execute for every public seat.
|
|
13
|
+
* Records nothing itself — the ledger seam already appends the original payload.
|
|
14
|
+
* Ends this tool call. Does not summon an auditor and does not end the role run.
|
|
15
|
+
* Shape is not an admission gate (ADR 0055). Seat reminders stay on beforeAccept.
|
|
16
|
+
*/
|
|
17
|
+
export function registerFiledSubmissionTool(
|
|
18
|
+
roleHost: RoleHost,
|
|
19
|
+
tool: RoleSubmissionDeclaration,
|
|
20
|
+
options: {
|
|
21
|
+
readonly readyError: () => string | undefined;
|
|
22
|
+
readonly beforeAccept?: FiledSubmissionBeforeAccept;
|
|
23
|
+
},
|
|
24
|
+
): void {
|
|
25
|
+
roleHost.registerTool({
|
|
26
|
+
name: tool.name,
|
|
27
|
+
label: tool.label,
|
|
28
|
+
description: tool.description,
|
|
29
|
+
...(tool.promptSnippet === undefined ? {} : { promptSnippet: tool.promptSnippet }),
|
|
30
|
+
parameters: tool.parameters as never,
|
|
31
|
+
async execute(toolCallId, parameters, signal, _onUpdate, ctx): Promise<HostToolResult<unknown>> {
|
|
32
|
+
const blocked = options.readyError();
|
|
33
|
+
if (blocked !== undefined) throw new Error(blocked);
|
|
34
|
+
const projected = options.beforeAccept === undefined
|
|
35
|
+
? undefined
|
|
36
|
+
: await options.beforeAccept({ toolCallId, parameters, signal, ctx });
|
|
37
|
+
return {
|
|
38
|
+
content: [],
|
|
39
|
+
details: projected === undefined ? parameters : projected,
|
|
40
|
+
terminate: true as const,
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
}
|
package/src/gatekeeper-role.ts
CHANGED
|
@@ -7,13 +7,12 @@ import type { NoReceiptLifecycleFacts } from "./receipt-delivery-policy.ts";
|
|
|
7
7
|
import { GatekeeperDecisionError, receivedDiscriminator, unreadableDiscriminatorNotice } from "./submission-errors.ts";
|
|
8
8
|
import { INSPECTOR_OUTPUT_TOOL_NAME } from "./inspector-contracts.ts";
|
|
9
9
|
import { REVIEW_QUEUE_STATUSES, REVIEW_SUBMISSION_OUTPUT_TOOL_NAME } from "./review-submission.ts";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
gatekeeperOutputSchema,
|
|
13
|
-
} from "./package-contracts/gatekeeper-output.ts";
|
|
10
|
+
import { GATEKEEPER_OUTPUT_TOOL_NAME } from "./package-contracts/gatekeeper-output.ts";
|
|
11
|
+
import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
|
|
14
12
|
import type { PublicSummonResult } from "./public-role-summons.ts";
|
|
15
13
|
import type { TerminalResult } from "./public-cli/terminal.ts";
|
|
16
14
|
import { runIdFromRunDirectory } from "./run-terminal-artifacts.ts";
|
|
15
|
+
import { isRecord } from "./unknown-value.ts";
|
|
17
16
|
export const INSPECTOR_OUTPUT_TOOL = INSPECTOR_OUTPUT_TOOL_NAME;
|
|
18
17
|
export const NOTARY_OUTPUT_TOOL = REVIEW_SUBMISSION_OUTPUT_TOOL_NAME;
|
|
19
18
|
|
|
@@ -157,13 +156,7 @@ export type SubmissionGateHostActions = {
|
|
|
157
156
|
* registration envelope — src/role-runtime.ts (ADR 0018). Schema authority is
|
|
158
157
|
* the shared contract module (with infrastructure-failure declaration).
|
|
159
158
|
*/
|
|
160
|
-
export const GATEKEEPER_TOOL_SPEC =
|
|
161
|
-
name: GATEKEEPER_OUTPUT_TOOL_NAME,
|
|
162
|
-
label: "门下省决议",
|
|
163
|
-
description: "门下省终局决议。status 为 dispatch 或 pass。",
|
|
164
|
-
promptSnippet: "门下省决议",
|
|
165
|
-
parameters: gatekeeperOutputSchema,
|
|
166
|
-
} as const;
|
|
159
|
+
export const GATEKEEPER_TOOL_SPEC = roleSubmissionDeclaration("gatekeeper");
|
|
167
160
|
|
|
168
161
|
export type GatekeeperRuntimeDependencies = {
|
|
169
162
|
loadSoul(): Promise<string>;
|
|
@@ -174,20 +167,11 @@ function failureReason(error: unknown): string {
|
|
|
174
167
|
return error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
175
168
|
}
|
|
176
169
|
|
|
177
|
-
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
178
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
170
|
/** Original decision bytes — no sentinel replacement (#836). */
|
|
182
171
|
function retainedReceipt(decision: unknown): unknown {
|
|
183
172
|
return decision;
|
|
184
173
|
}
|
|
185
174
|
|
|
186
|
-
function readRecord(value: unknown): Record<string, unknown> | undefined {
|
|
187
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return undefined;
|
|
188
|
-
return value as Record<string, unknown>;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
175
|
/**
|
|
192
176
|
* Read only the conclusion field for queueing (#753).
|
|
193
177
|
* converged | continue | escalate → queue signal + raw receipt.
|
|
@@ -206,10 +190,10 @@ function projectOfficerDecision(
|
|
|
206
190
|
fallbackStatus?: string,
|
|
207
191
|
): GatekeeperResult {
|
|
208
192
|
const receipt = retainedReceipt(decision);
|
|
209
|
-
const record =
|
|
210
|
-
const status =
|
|
211
|
-
(record
|
|
212
|
-
|
|
193
|
+
const record = isRecord(decision) ? decision : undefined;
|
|
194
|
+
const status = record !== undefined && Object.hasOwn(record, "status")
|
|
195
|
+
? receivedDiscriminator(record, "status")
|
|
196
|
+
: fallbackStatus;
|
|
213
197
|
const queueStatus = typeof status === "string" ? reviewQueueStatus(status) : undefined;
|
|
214
198
|
if (queueStatus === "converged") {
|
|
215
199
|
return { status: "converged", officer, receipt };
|
|
@@ -4,8 +4,36 @@
|
|
|
4
4
|
* No bounce / verdict channel (言不为狱). 原卷保真 (ADR 0055).
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { Type } from "typebox";
|
|
8
|
+
|
|
9
|
+
import { openToolObject } from "./open-tool-schema.ts";
|
|
10
|
+
import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
|
|
11
|
+
import { isRecord } from "./unknown-value.ts";
|
|
12
|
+
|
|
7
13
|
export const GLEANER_LEFT_OUTPUT_TOOL_NAME = "ak_gleaner_left_output";
|
|
8
14
|
|
|
15
|
+
// #836 r16 class 1: pointer/statement are LLM/human-read narrative content — no
|
|
16
|
+
// code branches on their presence.
|
|
17
|
+
// #1134: `status` alone is the trajectory field (src/gleaner-left-contracts.ts:38
|
|
18
|
+
// recognizes completed to settle); its word rides the description. findings is
|
|
19
|
+
// narrative content declared name + semantic description only — the array type
|
|
20
|
+
// and the nested pointer/statement object shape are deleted, because the package
|
|
21
|
+
// declaration IS the host's pre-dispatch validator. An empty findings list stays
|
|
22
|
+
// a lawful completion; an omitted one is now expressible too.
|
|
23
|
+
export const gleanerLeftOutputSchema = withTerminatingOutputDeclarations(
|
|
24
|
+
openToolObject(
|
|
25
|
+
Type.Object({
|
|
26
|
+
status: Type.Unknown({
|
|
27
|
+
description: "completed",
|
|
28
|
+
}),
|
|
29
|
+
findings: Type.Unknown({
|
|
30
|
+
description:
|
|
31
|
+
"弹章列表;每条可含 pointer(文件/行指针)与 statement(疑点陈述),原样留存。空列表合法完局;机器不判弹章是否成立。",
|
|
32
|
+
}),
|
|
33
|
+
}),
|
|
34
|
+
),
|
|
35
|
+
);
|
|
36
|
+
|
|
9
37
|
/** Internal transport: comparison-base revision for the unanchored merge-candidate diff. */
|
|
10
38
|
export const GLEANER_LEFT_BASE_FLAG = {
|
|
11
39
|
name: "ak-gleaner-left-base",
|
|
@@ -26,7 +54,7 @@ export type GleanerLeftOutput = {
|
|
|
26
54
|
};
|
|
27
55
|
|
|
28
56
|
export function validateRecordedGleanerLeftOutput(value: unknown): GleanerLeftOutput {
|
|
29
|
-
if (
|
|
57
|
+
if (!isRecord(value)) {
|
|
30
58
|
throw new Error("Gleaner-left output has no execution discriminator");
|
|
31
59
|
}
|
|
32
60
|
let status: unknown;
|
|
@@ -40,4 +68,3 @@ export function validateRecordedGleanerLeftOutput(value: unknown): GleanerLeftOu
|
|
|
40
68
|
}
|
|
41
69
|
throw new Error("Gleaner-left output has no execution discriminator");
|
|
42
70
|
}
|
|
43
|
-
|
package/src/gleaner-left-role.ts
CHANGED
|
@@ -1,42 +1,20 @@
|
|
|
1
1
|
import type { Static } from "typebox";
|
|
2
|
-
import { Type } from "typebox";
|
|
3
2
|
|
|
4
|
-
import { openToolObject } from "./open-tool-schema.ts";
|
|
5
|
-
import { withTerminatingOutputDeclarations } from "./package-contracts/terminating-infrastructure.ts";
|
|
6
3
|
import {
|
|
7
4
|
GLEANER_LEFT_OUTPUT_TOOL_NAME,
|
|
5
|
+
gleanerLeftOutputSchema,
|
|
8
6
|
validateRecordedGleanerLeftOutput,
|
|
9
7
|
type GleanerLeftOutput,
|
|
10
8
|
} from "./gleaner-left-contracts.ts";
|
|
9
|
+
import { roleSubmissionDeclaration } from "./role-submission-declarations.ts";
|
|
11
10
|
|
|
12
11
|
export {
|
|
13
12
|
GLEANER_LEFT_OUTPUT_TOOL_NAME,
|
|
13
|
+
gleanerLeftOutputSchema,
|
|
14
14
|
} from "./gleaner-left-contracts.ts";
|
|
15
15
|
export type { GleanerLeftOutput };
|
|
16
16
|
export { validateRecordedGleanerLeftOutput };
|
|
17
17
|
|
|
18
|
-
// #836 r16 class 1: pointer/statement are LLM/human-read narrative content — no
|
|
19
|
-
// code branches on their presence.
|
|
20
|
-
// #1134: `status` alone is the trajectory field (src/gleaner-left-contracts.ts:38
|
|
21
|
-
// recognizes completed to settle); its word rides the description. findings is
|
|
22
|
-
// narrative content declared name + semantic description only — the array type
|
|
23
|
-
// and the nested pointer/statement object shape are deleted, because the package
|
|
24
|
-
// declaration IS the host's pre-dispatch validator. An empty findings list stays
|
|
25
|
-
// a lawful completion; an omitted one is now expressible too.
|
|
26
|
-
export const gleanerLeftOutputSchema = withTerminatingOutputDeclarations(
|
|
27
|
-
openToolObject(
|
|
28
|
-
Type.Object({
|
|
29
|
-
status: Type.Unknown({
|
|
30
|
-
description: "completed",
|
|
31
|
-
}),
|
|
32
|
-
findings: Type.Unknown({
|
|
33
|
-
description:
|
|
34
|
-
"弹章列表;每条可含 pointer(文件/行指针)与 statement(疑点陈述),原样留存。空列表合法完局;机器不判弹章是否成立。",
|
|
35
|
-
}),
|
|
36
|
-
}),
|
|
37
|
-
),
|
|
38
|
-
);
|
|
39
|
-
|
|
40
18
|
export type GleanerLeftOutputParameters = Static<typeof gleanerLeftOutputSchema>;
|
|
41
19
|
|
|
42
20
|
export type GleanerLeftRuntimeDependencies = {
|
|
@@ -46,10 +24,4 @@ export type GleanerLeftRuntimeDependencies = {
|
|
|
46
24
|
/**
|
|
47
25
|
* 决定工具规格。生命周期装配归注册信封 owner——src/role-runtime.ts(ADR 0018)。
|
|
48
26
|
*/
|
|
49
|
-
export const GLEANER_LEFT_TOOL_SPEC =
|
|
50
|
-
name: GLEANER_LEFT_OUTPUT_TOOL_NAME,
|
|
51
|
-
label: "左拾遗输出",
|
|
52
|
-
description: "左拾遗弹章。",
|
|
53
|
-
promptSnippet: "左拾遗弹章",
|
|
54
|
-
parameters: gleanerLeftOutputSchema,
|
|
55
|
-
} as const;
|
|
27
|
+
export const GLEANER_LEFT_TOOL_SPEC = roleSubmissionDeclaration("gleaner-left");
|
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { HostIdentityDescription } from "../host-descriptions.ts";
|
|
8
8
|
|
|
9
|
+
import { isRecord } from "../unknown-value.ts";
|
|
10
|
+
|
|
9
11
|
/** Claude Code print-mode (#645). */
|
|
10
12
|
export type ClaudePrintHostDescription = HostIdentityDescription & Readonly<{
|
|
11
13
|
protocol: "claude-print";
|
|
@@ -167,7 +169,7 @@ export function closeJsonSchemaForCodex(
|
|
|
167
169
|
schema: Readonly<Record<string, unknown>>,
|
|
168
170
|
): Record<string, unknown> {
|
|
169
171
|
const closed = closeSchemaNode(schema) as Record<string, unknown>;
|
|
170
|
-
const existingDefs =
|
|
172
|
+
const existingDefs = isRecord(closed.$defs)
|
|
171
173
|
? (closed.$defs as Record<string, unknown>)
|
|
172
174
|
: {};
|
|
173
175
|
return {
|
|
@@ -179,14 +181,9 @@ export function closeJsonSchemaForCodex(
|
|
|
179
181
|
};
|
|
180
182
|
}
|
|
181
183
|
|
|
182
|
-
/** Shared plain-object guard for headless host schema/event reduction. */
|
|
183
|
-
export function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
184
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
184
|
/** Pure null leaf only — composite type|null is stripped in-leaf, not dropped whole. */
|
|
188
185
|
function isPureNullTypeSchema(value: unknown): boolean {
|
|
189
|
-
if (!
|
|
186
|
+
if (!isRecord(value)) return false;
|
|
190
187
|
if (value.type === "null") return true;
|
|
191
188
|
if (Array.isArray(value.type) && value.type.length > 0 && value.type.every((t) => t === "null")) {
|
|
192
189
|
return true;
|
|
@@ -199,7 +196,7 @@ function isPureNullTypeSchema(value: unknown): boolean {
|
|
|
199
196
|
* Required edges keep the non-null type(s); optional edges re-add null once.
|
|
200
197
|
*/
|
|
201
198
|
function stripNullFromLeafType(leaf: unknown): unknown {
|
|
202
|
-
if (!
|
|
199
|
+
if (!isRecord(leaf) || !Array.isArray(leaf.type)) return leaf;
|
|
203
200
|
const nonNull = leaf.type.filter((t) => t !== "null");
|
|
204
201
|
if (nonNull.length === leaf.type.length) return leaf;
|
|
205
202
|
if (nonNull.length === 0) return { ...leaf, type: "null" };
|
|
@@ -213,7 +210,7 @@ function stripNullFromLeafType(leaf: unknown): unknown {
|
|
|
213
210
|
* lack `type`; strict structured output rejects those intermediate nodes.
|
|
214
211
|
*/
|
|
215
212
|
function flattenUnionLeaves(schema: unknown): unknown[] {
|
|
216
|
-
if (!
|
|
213
|
+
if (!isRecord(schema)) return [schema];
|
|
217
214
|
if (Array.isArray(schema.anyOf)) {
|
|
218
215
|
return schema.anyOf.flatMap(flattenUnionLeaves);
|
|
219
216
|
}
|
|
@@ -233,7 +230,7 @@ function nonNullLeaves(schema: unknown): unknown[] {
|
|
|
233
230
|
*/
|
|
234
231
|
function closePropertySchema(schema: unknown, optional: boolean): unknown {
|
|
235
232
|
const description =
|
|
236
|
-
|
|
233
|
+
isRecord(schema) && typeof schema.description === "string"
|
|
237
234
|
? { description: schema.description }
|
|
238
235
|
: {};
|
|
239
236
|
const leaves = nonNullLeaves(schema).map(closeSchemaNode);
|
|
@@ -242,10 +239,10 @@ function closePropertySchema(schema: unknown, optional: boolean): unknown {
|
|
|
242
239
|
const closed = leaves.length === 1 ? leaves[0] : { anyOf: leaves };
|
|
243
240
|
// Codex strict does not allow sibling keys on $ref; put property guidance
|
|
244
241
|
// on an enclosing schema instead of losing it or weakening the free-JSON ref.
|
|
245
|
-
if (
|
|
242
|
+
if (isRecord(closed) && typeof closed.$ref === "string") {
|
|
246
243
|
return { anyOf: [closed], ...description };
|
|
247
244
|
}
|
|
248
|
-
return
|
|
245
|
+
return isRecord(closed) ? { ...closed, ...description } : closed;
|
|
249
246
|
}
|
|
250
247
|
return { anyOf: [...leaves, { type: "null" }], ...description };
|
|
251
248
|
}
|
|
@@ -258,7 +255,7 @@ function closePropertySchema(schema: unknown, optional: boolean): unknown {
|
|
|
258
255
|
function ensureTypedLeaf(schema: Record<string, unknown>): Record<string, unknown> {
|
|
259
256
|
if (schema.type !== undefined) return schema;
|
|
260
257
|
if (typeof schema.$ref === "string") return schema;
|
|
261
|
-
if (
|
|
258
|
+
if (isRecord(schema.properties) || schema.additionalProperties !== undefined) {
|
|
262
259
|
return { ...schema, type: "object" };
|
|
263
260
|
}
|
|
264
261
|
if (schema.items !== undefined) {
|
|
@@ -287,7 +284,7 @@ function originalRequiredNames(node: Record<string, unknown>): ReadonlySet<strin
|
|
|
287
284
|
}
|
|
288
285
|
|
|
289
286
|
function closeSchemaNode(node: unknown): unknown {
|
|
290
|
-
if (!
|
|
287
|
+
if (!isRecord(node)) return node;
|
|
291
288
|
|
|
292
289
|
// Already a ref (free-JSON leaf or pre-existing) — do not retype.
|
|
293
290
|
if (typeof node.$ref === "string") return node;
|
|
@@ -305,13 +302,13 @@ function closeSchemaNode(node: unknown): unknown {
|
|
|
305
302
|
if (node.items !== undefined) {
|
|
306
303
|
out.items = closeSchemaNode(node.items);
|
|
307
304
|
}
|
|
308
|
-
if (
|
|
305
|
+
if (isRecord(node.$defs)) {
|
|
309
306
|
out.$defs = Object.fromEntries(
|
|
310
307
|
Object.entries(node.$defs).map(([key, value]) => [key, closeSchemaNode(value)]),
|
|
311
308
|
);
|
|
312
309
|
}
|
|
313
310
|
|
|
314
|
-
const hasProperties =
|
|
311
|
+
const hasProperties = isRecord(node.properties);
|
|
315
312
|
const isObjectType =
|
|
316
313
|
node.type === "object"
|
|
317
314
|
|| (Array.isArray(node.type) && node.type.includes("object"))
|
|
@@ -351,10 +348,10 @@ function closeSchemaNode(node: unknown): unknown {
|
|
|
351
348
|
function stringEnvironment(value: unknown): Record<string, string> | undefined {
|
|
352
349
|
const entries = Array.isArray(value)
|
|
353
350
|
? value.flatMap((item) => {
|
|
354
|
-
if (!
|
|
351
|
+
if (!isRecord(item) || typeof item.name !== "string" || typeof item.value !== "string") return [];
|
|
355
352
|
return [[item.name, item.value] as const];
|
|
356
353
|
})
|
|
357
|
-
:
|
|
354
|
+
: isRecord(value)
|
|
358
355
|
? Object.entries(value).filter((entry): entry is [string, string] => typeof entry[1] === "string")
|
|
359
356
|
: [];
|
|
360
357
|
return entries.length === 0 ? undefined : Object.fromEntries(entries);
|
|
@@ -36,11 +36,12 @@ import {
|
|
|
36
36
|
headlessTurnArgs,
|
|
37
37
|
isClaudePrintDescription,
|
|
38
38
|
isCodexExecDescription,
|
|
39
|
-
isPlainObject,
|
|
40
39
|
type HeadlessHostDescription,
|
|
41
40
|
} from "./description.ts";
|
|
42
41
|
import { NAVIGATOR_OUTPUT_TOOL_NAME } from "../package-contracts/navigator-output.ts";
|
|
43
42
|
|
|
43
|
+
import { errorText, isRecord } from "../unknown-value.ts";
|
|
44
|
+
|
|
44
45
|
export type HeadlessRoleTurnHostConfig = Readonly<{
|
|
45
46
|
description: HeadlessHostDescription;
|
|
46
47
|
sessionIdentity: SessionIdentityAuthority;
|
|
@@ -68,7 +69,7 @@ export type HeadlessCliResult = Readonly<{
|
|
|
68
69
|
* envelope without stream-json `type`). Intermediate stream-json events are not.
|
|
69
70
|
*/
|
|
70
71
|
function isHeadlessResultCandidate(value: unknown): value is HeadlessCliResult {
|
|
71
|
-
if (!
|
|
72
|
+
if (!isRecord(value)) return false;
|
|
72
73
|
const record = value as HeadlessCliResult & { type?: unknown };
|
|
73
74
|
return record.type === undefined
|
|
74
75
|
|| record.type === "result"
|
|
@@ -131,11 +132,11 @@ function createCodexExecTurnObserver(): {
|
|
|
131
132
|
|
|
132
133
|
return {
|
|
133
134
|
observe(value) {
|
|
134
|
-
if (!
|
|
135
|
+
if (!isRecord(value)) return;
|
|
135
136
|
const type = typeof value.type === "string" ? value.type : undefined;
|
|
136
137
|
if (type === "thread.started" && typeof value.thread_id === "string" && value.thread_id !== "") {
|
|
137
138
|
threadId = value.thread_id;
|
|
138
|
-
} else if (type === "item.completed" &&
|
|
139
|
+
} else if (type === "item.completed" && isRecord(value.item)) {
|
|
139
140
|
if (value.item.type === "agent_message" && typeof value.item.text === "string") {
|
|
140
141
|
finalMessage = value.item.text;
|
|
141
142
|
}
|
|
@@ -161,7 +162,7 @@ function createCodexExecTurnObserver(): {
|
|
|
161
162
|
|
|
162
163
|
function formatCodexFailurePayload(payload: unknown): string {
|
|
163
164
|
if (typeof payload === "string" && payload.trim() !== "") return payload;
|
|
164
|
-
if (
|
|
165
|
+
if (isRecord(payload)) {
|
|
165
166
|
if (typeof payload.message === "string" && payload.message.trim() !== "") return payload.message;
|
|
166
167
|
try {
|
|
167
168
|
return JSON.stringify(payload);
|
|
@@ -198,7 +199,7 @@ function resolveGitCommonDir(cwd: string): string | undefined {
|
|
|
198
199
|
encoding: "utf8",
|
|
199
200
|
});
|
|
200
201
|
} catch (error) {
|
|
201
|
-
const message =
|
|
202
|
+
const message = errorText(error);
|
|
202
203
|
throw new Error(`git rev-parse --git-common-dir failed: ${message}`);
|
|
203
204
|
}
|
|
204
205
|
if (result.error !== undefined) {
|
|
@@ -487,7 +488,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
487
488
|
...(gitCommonDir === undefined ? {} : { writableRoots: [gitCommonDir] }),
|
|
488
489
|
});
|
|
489
490
|
} catch (error) {
|
|
490
|
-
const message =
|
|
491
|
+
const message = errorText(error);
|
|
491
492
|
return {
|
|
492
493
|
status: "terminal",
|
|
493
494
|
result: failure("session", "HeadlessArgvFailure", "argv-failed", { diagnostic: message }, message),
|
|
@@ -559,7 +560,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
559
560
|
} catch (error) {
|
|
560
561
|
if (childExited) exitedSessionId = (codex ? codexObserver?.result().threadId : undefined) ?? sessionId;
|
|
561
562
|
if (isHostAbortedError(error)) throw error;
|
|
562
|
-
const message =
|
|
563
|
+
const message = errorText(error);
|
|
563
564
|
const observedHostFailure = codexObserver?.result().failureDiagnostic;
|
|
564
565
|
if (observedHostFailure !== undefined) {
|
|
565
566
|
return {
|
|
@@ -604,7 +605,7 @@ export function createHeadlessRoleTurnHost(config: HeadlessRoleTurnHostConfig):
|
|
|
604
605
|
} catch (error) {
|
|
605
606
|
// The host failure remains primary; retain persistence failure as
|
|
606
607
|
// secondary typed evidence instead of replacing the terminal.
|
|
607
|
-
sessionBindingDiagnostic =
|
|
608
|
+
sessionBindingDiagnostic = errorText(error);
|
|
608
609
|
}
|
|
609
610
|
}
|
|
610
611
|
|