@akagilnc/pi-workflow-roles 0.1.3652 → 0.1.3661
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +0 -2
- package/dist/acp-host/production-host.js +1056 -1236
- package/dist/public-cli/case-dossier-delivery.js +43 -0
- package/dist/public-cli/inspector-run.js +9 -6
- package/dist/public-cli/instruction-seat-run.js +24 -24
- package/dist/public-cli/invocation.js +2 -24
- package/dist/public-cli/main.js +1876 -2159
- package/dist/public-cli/post-admission.js +38 -12
- package/dist/public-cli/run-lifecycle.js +25 -14
- package/dist/public-cli/seat-ticket-binding.js +93 -70
- package/package.json +1 -1
- package/src/public-cli/case-dossier-delivery.ts +51 -0
- package/src/public-cli/countersign-run.ts +14 -17
- package/src/public-cli/diarist-run.ts +11 -13
- package/src/public-cli/inspector-run.ts +9 -10
- package/src/public-cli/instruction-seat-run.ts +28 -35
- package/src/public-cli/invocation.ts +4 -46
- package/src/public-cli/post-admission.ts +41 -6
- package/src/public-cli/run-lifecycle.ts +29 -21
- package/src/public-cli/seat-ticket-binding.ts +108 -104
- package/dist/diarist-ticket-resolution.js +0 -177
- package/resources/diarist-resolve-ticket.md +0 -39
- package/src/diarist-ticket-resolution.ts +0 -302
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 起居郎 pre-court ticket resolution (#582 / diarist-resolves-ticket-llm-layer).
|
|
3
|
-
* Unbound countersign: LLM typed assertion over accepted instruction, then two
|
|
4
|
-
* mechanical checks on N (complete decimal number in instruction; live GitHub issue).
|
|
5
|
-
* Verification failure is controlled failure — never washed to true-unbound.
|
|
6
|
-
*/
|
|
7
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
-
import { join } from "node:path";
|
|
9
|
-
import { fileURLToPath } from "node:url";
|
|
10
|
-
import { createGhApiRunner, projectGhIssueBody, } from "./collector-github.js";
|
|
11
|
-
import { ENGINE_DETOUR_STAGED_PROMPT_TOKEN, runEngineDetourOnce, } from "./engine-detour.js";
|
|
12
|
-
export const DIARIST_RESOLVE_TICKET_METHOD_RELATIVE = "resources/diarist-resolve-ticket.md";
|
|
13
|
-
export class DiaristTicketResolutionError extends Error {
|
|
14
|
-
code = "diarist-ticket-resolution";
|
|
15
|
-
reason;
|
|
16
|
-
constructor(reason, message, options) {
|
|
17
|
-
super(message, options);
|
|
18
|
-
this.name = "DiaristTicketResolutionError";
|
|
19
|
-
this.reason = reason;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
export function resolveDiaristResolveTicketMethodPath(packageRoot = fileURLToPath(new URL("..", import.meta.url))) {
|
|
23
|
-
return join(packageRoot, DIARIST_RESOLVE_TICKET_METHOD_RELATIVE);
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Decision-key exact identity: complete decimal number N appears in instruction.
|
|
27
|
-
* A longer number's digit substring (e.g. 82 inside #582) is not N.
|
|
28
|
-
*/
|
|
29
|
-
export function instructionContainsTicketNumber(instruction, ticketNumber) {
|
|
30
|
-
if (!Number.isSafeInteger(ticketNumber) || ticketNumber < 1)
|
|
31
|
-
return false;
|
|
32
|
-
// Complete number token: not preceded or followed by another digit.
|
|
33
|
-
return new RegExp(`(?<!\\d)${String(ticketNumber)}(?!\\d)`).test(instruction);
|
|
34
|
-
}
|
|
35
|
-
/** Sole stdout shapes: {assertion:"ticket",ticketNumber:N} | {assertion:"true-unbound"}. */
|
|
36
|
-
export function parseDiaristTicketResolverStdout(stdout) {
|
|
37
|
-
const trimmed = stdout.trim();
|
|
38
|
-
if (trimmed.length === 0) {
|
|
39
|
-
throw new DiaristTicketResolutionError("empty-stdout", "diarist ticket resolver stdout is empty");
|
|
40
|
-
}
|
|
41
|
-
let parsed;
|
|
42
|
-
try {
|
|
43
|
-
parsed = JSON.parse(trimmed);
|
|
44
|
-
}
|
|
45
|
-
catch (error) {
|
|
46
|
-
throw new DiaristTicketResolutionError("unparseable-json", "diarist ticket resolver stdout is not JSON", { cause: error });
|
|
47
|
-
}
|
|
48
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
49
|
-
throw new DiaristTicketResolutionError("not-object", "diarist ticket resolver stdout must be one JSON object");
|
|
50
|
-
}
|
|
51
|
-
const record = parsed;
|
|
52
|
-
if (!Object.prototype.hasOwnProperty.call(record, "assertion")) {
|
|
53
|
-
throw new DiaristTicketResolutionError("assertion-missing", "diarist ticket resolver stdout missing assertion");
|
|
54
|
-
}
|
|
55
|
-
if (record.assertion === "true-unbound")
|
|
56
|
-
return { kind: "true-unbound" };
|
|
57
|
-
const n = record.ticketNumber;
|
|
58
|
-
if (record.assertion === "ticket" &&
|
|
59
|
-
typeof n === "number" &&
|
|
60
|
-
Number.isSafeInteger(n) &&
|
|
61
|
-
n >= 1) {
|
|
62
|
-
return { kind: "ticket", ticketNumber: n };
|
|
63
|
-
}
|
|
64
|
-
throw new DiaristTicketResolutionError("assertion-uninterpretable", "diarist ticket resolver assertion is not ticket|true-unbound with safe ticketNumber");
|
|
65
|
-
}
|
|
66
|
-
const HERMES_RESOLVER_TOOLSET = "context_engine";
|
|
67
|
-
/** Hermes resolver: method bytes + instruction only; empty toolset; staged prompt. */
|
|
68
|
-
export function createHermesDiaristTicketResolver(options = {}) {
|
|
69
|
-
const methodPath = resolveDiaristResolveTicketMethodPath(options.packageRoot);
|
|
70
|
-
if (!existsSync(methodPath)) {
|
|
71
|
-
throw new Error(`diarist resolve-ticket method material missing (${methodPath})`);
|
|
72
|
-
}
|
|
73
|
-
const method = readFileSync(methodPath, "utf8");
|
|
74
|
-
if (method.trim().length === 0) {
|
|
75
|
-
throw new Error(`diarist resolve-ticket method material is empty (${methodPath})`);
|
|
76
|
-
}
|
|
77
|
-
const executable = options.executable ?? "hermes";
|
|
78
|
-
return async (input) => {
|
|
79
|
-
const argv = [
|
|
80
|
-
executable,
|
|
81
|
-
"chat",
|
|
82
|
-
"--query-file",
|
|
83
|
-
ENGINE_DETOUR_STAGED_PROMPT_TOKEN,
|
|
84
|
-
"-Q",
|
|
85
|
-
"--no-restore-cwd",
|
|
86
|
-
"--ignore-rules",
|
|
87
|
-
"-t",
|
|
88
|
-
HERMES_RESOLVER_TOOLSET,
|
|
89
|
-
];
|
|
90
|
-
let result;
|
|
91
|
-
try {
|
|
92
|
-
result = await runEngineDetourOnce({
|
|
93
|
-
argv,
|
|
94
|
-
stagedPrompt: JSON.stringify({ method, instruction: input.instruction }),
|
|
95
|
-
cwd: options.cwd ?? process.cwd(),
|
|
96
|
-
...(options.env === undefined ? {} : { env: options.env }),
|
|
97
|
-
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
catch (error) {
|
|
101
|
-
throw new DiaristTicketResolutionError("engine-failed", "diarist ticket resolver engine failed to launch", { cause: error });
|
|
102
|
-
}
|
|
103
|
-
if (result.code !== 0) {
|
|
104
|
-
throw new DiaristTicketResolutionError("engine-failed", `diarist ticket resolver engine exited ${result.code}: ${result.stderr.slice(0, 500)}`);
|
|
105
|
-
}
|
|
106
|
-
return parseDiaristTicketResolverStdout(result.stdout);
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
/** Live ticket check over shared gh issue-body projection. */
|
|
110
|
-
export function createGhTicketExistenceChecker(options) {
|
|
111
|
-
const runner = options?.runner ?? createGhApiRunner();
|
|
112
|
-
return async (input) => {
|
|
113
|
-
const projected = await projectGhIssueBody(runner, {
|
|
114
|
-
owner: input.owner,
|
|
115
|
-
repo: input.repo,
|
|
116
|
-
ticketNumber: input.ticketNumber,
|
|
117
|
-
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
118
|
-
});
|
|
119
|
-
if (projected.status === "available")
|
|
120
|
-
return true;
|
|
121
|
-
if (projected.status === "unavailable"
|
|
122
|
-
&& projected.reason === "http-non-2xx"
|
|
123
|
-
&& projected.httpStatus === 404) {
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
if (projected.status === "unavailable" && projected.reason === "pull-request") {
|
|
127
|
-
throw new DiaristTicketResolutionError("ticket-is-pull-request", `ticket #${input.ticketNumber} resolves to a pull request, not an issue`, { cause: projected });
|
|
128
|
-
}
|
|
129
|
-
if (projected.status === "unavailable") {
|
|
130
|
-
throw new DiaristTicketResolutionError("ticket-unavailable", `ticket verification unavailable for ${input.owner}/${input.repo}#${input.ticketNumber}`, { cause: projected });
|
|
131
|
-
}
|
|
132
|
-
throw new DiaristTicketResolutionError("ticket-invalid", `ticket verification returned an invalid issue payload for ${input.owner}/${input.repo}#${input.ticketNumber}`, { cause: projected });
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
/** Mechanical verify after LLM assertion. true-unbound passes; ticket needs both checks. */
|
|
136
|
-
export async function verifyDiaristTicketAssertion(input) {
|
|
137
|
-
if (input.assertion.kind === "true-unbound")
|
|
138
|
-
return input.assertion;
|
|
139
|
-
const n = input.assertion.ticketNumber;
|
|
140
|
-
if (!instructionContainsTicketNumber(input.instruction, n)) {
|
|
141
|
-
throw new DiaristTicketResolutionError("number-not-in-instruction", `diarist ticket assertion #${n} complete decimal number does not appear in accepted instruction`);
|
|
142
|
-
}
|
|
143
|
-
if (input.origin === undefined) {
|
|
144
|
-
throw new DiaristTicketResolutionError("origin-unresolved", `diarist ticket assertion #${n} requires a resolvable github.com origin remote`);
|
|
145
|
-
}
|
|
146
|
-
let exists;
|
|
147
|
-
try {
|
|
148
|
-
exists = await input.checkExistence({
|
|
149
|
-
owner: input.origin.owner,
|
|
150
|
-
repo: input.origin.repo,
|
|
151
|
-
ticketNumber: n,
|
|
152
|
-
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
catch (error) {
|
|
156
|
-
if (error instanceof DiaristTicketResolutionError)
|
|
157
|
-
throw error;
|
|
158
|
-
throw new DiaristTicketResolutionError("ticket-unavailable", `diarist ticket assertion #${n} live verification failed`, { cause: error });
|
|
159
|
-
}
|
|
160
|
-
if (!exists) {
|
|
161
|
-
throw new DiaristTicketResolutionError("ticket-missing", `diarist ticket assertion #${n} does not exist as a live issue on ${input.origin.owner}/${input.origin.repo}`);
|
|
162
|
-
}
|
|
163
|
-
return input.assertion;
|
|
164
|
-
}
|
|
165
|
-
export async function resolveDiaristTicketFromInstruction(input) {
|
|
166
|
-
const assertion = await input.resolver({
|
|
167
|
-
instruction: input.instruction,
|
|
168
|
-
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
169
|
-
});
|
|
170
|
-
return verifyDiaristTicketAssertion({
|
|
171
|
-
assertion,
|
|
172
|
-
instruction: input.instruction,
|
|
173
|
-
origin: input.origin,
|
|
174
|
-
checkExistence: input.checkExistence,
|
|
175
|
-
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
176
|
-
});
|
|
177
|
-
}
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
# 起居郎认票方法(owner-domain)
|
|
2
|
-
|
|
3
|
-
本文件是起居郎 LLM 认票推理的语义方法真源(decision key `diarist-resolves-ticket-llm-layer` / #582)。机器经共享 engine 接缝 staged prompt 递送本文件字节与已受理 instruction(不入 argv);判断以本方法为准。
|
|
4
|
-
|
|
5
|
-
## 任务
|
|
6
|
-
|
|
7
|
-
只根据已受理 instruction,断言本庭的票面身份:
|
|
8
|
-
|
|
9
|
-
- 认出明确票号 N → 断言 `ticket`
|
|
10
|
-
- 真无票(instruction 不指向任何具体票)→ 断言 `true-unbound`
|
|
11
|
-
|
|
12
|
-
## 何谓认出票号
|
|
13
|
-
|
|
14
|
-
instruction 明确以本庭任务绑定某一 GitHub issue / 票号时,才断言 `ticket`。常见形态包括但不限于:
|
|
15
|
-
|
|
16
|
-
- 「票 #582」「#582」「issue 582」
|
|
17
|
-
- 明确写出要审/修/续的票号
|
|
18
|
-
|
|
19
|
-
拿不准、只是顺带提到他票、或 instruction 本身是无票的一般性问询 → 断言 `true-unbound`。**不得猜测**。
|
|
20
|
-
|
|
21
|
-
## 边界
|
|
22
|
-
|
|
23
|
-
- 只读 instruction;不得假设附件、仓库状态或历史 run。
|
|
24
|
-
- 相关性/授权内容不归你管——你只回答「是哪张票 / 真无票」。
|
|
25
|
-
- 不得输出多个票号;若 instruction 含多个候选且无法唯一确定,断言 `true-unbound`。
|
|
26
|
-
|
|
27
|
-
## 输出
|
|
28
|
-
|
|
29
|
-
只输出一个 JSON 对象,二者择一:
|
|
30
|
-
|
|
31
|
-
```json
|
|
32
|
-
{"assertion":"ticket","ticketNumber":582}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
```json
|
|
36
|
-
{"assertion":"true-unbound"}
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
`ticketNumber` 必须是正整数。不要输出 JSON 以外的文字。
|
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 起居郎 pre-court ticket resolution (#582 / diarist-resolves-ticket-llm-layer).
|
|
3
|
-
* Unbound countersign: LLM typed assertion over accepted instruction, then two
|
|
4
|
-
* mechanical checks on N (complete decimal number in instruction; live GitHub issue).
|
|
5
|
-
* Verification failure is controlled failure — never washed to true-unbound.
|
|
6
|
-
*/
|
|
7
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
8
|
-
import { join } from "node:path";
|
|
9
|
-
import { fileURLToPath } from "node:url";
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
createGhApiRunner,
|
|
13
|
-
projectGhIssueBody,
|
|
14
|
-
type GhApiRunner,
|
|
15
|
-
} from "./collector-github.ts";
|
|
16
|
-
import {
|
|
17
|
-
ENGINE_DETOUR_STAGED_PROMPT_TOKEN,
|
|
18
|
-
runEngineDetourOnce,
|
|
19
|
-
} from "./engine-detour.ts";
|
|
20
|
-
|
|
21
|
-
export const DIARIST_RESOLVE_TICKET_METHOD_RELATIVE =
|
|
22
|
-
"resources/diarist-resolve-ticket.md" as const;
|
|
23
|
-
|
|
24
|
-
export type DiaristTicketAssertion =
|
|
25
|
-
| { readonly kind: "ticket"; readonly ticketNumber: number }
|
|
26
|
-
| { readonly kind: "true-unbound" };
|
|
27
|
-
|
|
28
|
-
export type DiaristTicketResolution = DiaristTicketAssertion;
|
|
29
|
-
|
|
30
|
-
export type DiaristTicketResolver = (input: {
|
|
31
|
-
readonly instruction: string;
|
|
32
|
-
readonly signal?: AbortSignal;
|
|
33
|
-
}) => Promise<DiaristTicketAssertion>;
|
|
34
|
-
|
|
35
|
-
export type TicketExistenceChecker = (input: {
|
|
36
|
-
readonly owner: string;
|
|
37
|
-
readonly repo: string;
|
|
38
|
-
readonly ticketNumber: number;
|
|
39
|
-
readonly signal?: AbortSignal;
|
|
40
|
-
}) => Promise<boolean>;
|
|
41
|
-
|
|
42
|
-
export type DiaristTicketResolutionReason =
|
|
43
|
-
| "empty-stdout"
|
|
44
|
-
| "unparseable-json"
|
|
45
|
-
| "not-object"
|
|
46
|
-
| "assertion-missing"
|
|
47
|
-
| "assertion-uninterpretable"
|
|
48
|
-
| "number-not-in-instruction"
|
|
49
|
-
| "ticket-missing"
|
|
50
|
-
| "ticket-is-pull-request"
|
|
51
|
-
| "ticket-unavailable"
|
|
52
|
-
| "ticket-invalid"
|
|
53
|
-
| "origin-unresolved"
|
|
54
|
-
| "engine-failed";
|
|
55
|
-
|
|
56
|
-
export class DiaristTicketResolutionError extends Error {
|
|
57
|
-
readonly code = "diarist-ticket-resolution" as const;
|
|
58
|
-
readonly reason: DiaristTicketResolutionReason;
|
|
59
|
-
constructor(
|
|
60
|
-
reason: DiaristTicketResolutionReason,
|
|
61
|
-
message: string,
|
|
62
|
-
options?: ErrorOptions,
|
|
63
|
-
) {
|
|
64
|
-
super(message, options);
|
|
65
|
-
this.name = "DiaristTicketResolutionError";
|
|
66
|
-
this.reason = reason;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function resolveDiaristResolveTicketMethodPath(
|
|
71
|
-
packageRoot: string = fileURLToPath(new URL("..", import.meta.url)),
|
|
72
|
-
): string {
|
|
73
|
-
return join(packageRoot, DIARIST_RESOLVE_TICKET_METHOD_RELATIVE);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Decision-key exact identity: complete decimal number N appears in instruction.
|
|
78
|
-
* A longer number's digit substring (e.g. 82 inside #582) is not N.
|
|
79
|
-
*/
|
|
80
|
-
export function instructionContainsTicketNumber(
|
|
81
|
-
instruction: string,
|
|
82
|
-
ticketNumber: number,
|
|
83
|
-
): boolean {
|
|
84
|
-
if (!Number.isSafeInteger(ticketNumber) || ticketNumber < 1) return false;
|
|
85
|
-
// Complete number token: not preceded or followed by another digit.
|
|
86
|
-
return new RegExp(`(?<!\\d)${String(ticketNumber)}(?!\\d)`).test(instruction);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/** Sole stdout shapes: {assertion:"ticket",ticketNumber:N} | {assertion:"true-unbound"}. */
|
|
90
|
-
export function parseDiaristTicketResolverStdout(
|
|
91
|
-
stdout: string,
|
|
92
|
-
): DiaristTicketAssertion {
|
|
93
|
-
const trimmed = stdout.trim();
|
|
94
|
-
if (trimmed.length === 0) {
|
|
95
|
-
throw new DiaristTicketResolutionError(
|
|
96
|
-
"empty-stdout",
|
|
97
|
-
"diarist ticket resolver stdout is empty",
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
let parsed: unknown;
|
|
101
|
-
try {
|
|
102
|
-
parsed = JSON.parse(trimmed);
|
|
103
|
-
} catch (error) {
|
|
104
|
-
throw new DiaristTicketResolutionError(
|
|
105
|
-
"unparseable-json",
|
|
106
|
-
"diarist ticket resolver stdout is not JSON",
|
|
107
|
-
{ cause: error },
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
111
|
-
throw new DiaristTicketResolutionError(
|
|
112
|
-
"not-object",
|
|
113
|
-
"diarist ticket resolver stdout must be one JSON object",
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
const record = parsed as Record<string, unknown>;
|
|
117
|
-
if (!Object.prototype.hasOwnProperty.call(record, "assertion")) {
|
|
118
|
-
throw new DiaristTicketResolutionError(
|
|
119
|
-
"assertion-missing",
|
|
120
|
-
"diarist ticket resolver stdout missing assertion",
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
if (record.assertion === "true-unbound") return { kind: "true-unbound" };
|
|
124
|
-
const n = record.ticketNumber;
|
|
125
|
-
if (
|
|
126
|
-
record.assertion === "ticket" &&
|
|
127
|
-
typeof n === "number" &&
|
|
128
|
-
Number.isSafeInteger(n) &&
|
|
129
|
-
n >= 1
|
|
130
|
-
) {
|
|
131
|
-
return { kind: "ticket", ticketNumber: n };
|
|
132
|
-
}
|
|
133
|
-
throw new DiaristTicketResolutionError(
|
|
134
|
-
"assertion-uninterpretable",
|
|
135
|
-
"diarist ticket resolver assertion is not ticket|true-unbound with safe ticketNumber",
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const HERMES_RESOLVER_TOOLSET = "context_engine" as const;
|
|
140
|
-
|
|
141
|
-
/** Hermes resolver: method bytes + instruction only; empty toolset; staged prompt. */
|
|
142
|
-
export function createHermesDiaristTicketResolver(options: {
|
|
143
|
-
readonly executable?: string;
|
|
144
|
-
readonly cwd?: string;
|
|
145
|
-
readonly env?: NodeJS.ProcessEnv;
|
|
146
|
-
readonly packageRoot?: string;
|
|
147
|
-
} = {}): DiaristTicketResolver {
|
|
148
|
-
const methodPath = resolveDiaristResolveTicketMethodPath(options.packageRoot);
|
|
149
|
-
if (!existsSync(methodPath)) {
|
|
150
|
-
throw new Error(`diarist resolve-ticket method material missing (${methodPath})`);
|
|
151
|
-
}
|
|
152
|
-
const method = readFileSync(methodPath, "utf8");
|
|
153
|
-
if (method.trim().length === 0) {
|
|
154
|
-
throw new Error(`diarist resolve-ticket method material is empty (${methodPath})`);
|
|
155
|
-
}
|
|
156
|
-
const executable = options.executable ?? "hermes";
|
|
157
|
-
return async (input) => {
|
|
158
|
-
const argv = [
|
|
159
|
-
executable,
|
|
160
|
-
"chat",
|
|
161
|
-
"--query-file",
|
|
162
|
-
ENGINE_DETOUR_STAGED_PROMPT_TOKEN,
|
|
163
|
-
"-Q",
|
|
164
|
-
"--no-restore-cwd",
|
|
165
|
-
"--ignore-rules",
|
|
166
|
-
"-t",
|
|
167
|
-
HERMES_RESOLVER_TOOLSET,
|
|
168
|
-
];
|
|
169
|
-
let result: Awaited<ReturnType<typeof runEngineDetourOnce>>;
|
|
170
|
-
try {
|
|
171
|
-
result = await runEngineDetourOnce({
|
|
172
|
-
argv,
|
|
173
|
-
stagedPrompt: JSON.stringify({ method, instruction: input.instruction }),
|
|
174
|
-
cwd: options.cwd ?? process.cwd(),
|
|
175
|
-
...(options.env === undefined ? {} : { env: options.env }),
|
|
176
|
-
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
177
|
-
});
|
|
178
|
-
} catch (error) {
|
|
179
|
-
throw new DiaristTicketResolutionError(
|
|
180
|
-
"engine-failed",
|
|
181
|
-
"diarist ticket resolver engine failed to launch",
|
|
182
|
-
{ cause: error },
|
|
183
|
-
);
|
|
184
|
-
}
|
|
185
|
-
if (result.code !== 0) {
|
|
186
|
-
throw new DiaristTicketResolutionError(
|
|
187
|
-
"engine-failed",
|
|
188
|
-
`diarist ticket resolver engine exited ${result.code}: ${result.stderr.slice(0, 500)}`,
|
|
189
|
-
);
|
|
190
|
-
}
|
|
191
|
-
return parseDiaristTicketResolverStdout(result.stdout);
|
|
192
|
-
};
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
/** Live ticket check over shared gh issue-body projection. */
|
|
196
|
-
export function createGhTicketExistenceChecker(options?: {
|
|
197
|
-
readonly runner?: GhApiRunner;
|
|
198
|
-
}): TicketExistenceChecker {
|
|
199
|
-
const runner = options?.runner ?? createGhApiRunner();
|
|
200
|
-
return async (input) => {
|
|
201
|
-
const projected = await projectGhIssueBody(runner, {
|
|
202
|
-
owner: input.owner,
|
|
203
|
-
repo: input.repo,
|
|
204
|
-
ticketNumber: input.ticketNumber,
|
|
205
|
-
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
206
|
-
});
|
|
207
|
-
if (projected.status === "available") return true;
|
|
208
|
-
if (
|
|
209
|
-
projected.status === "unavailable"
|
|
210
|
-
&& projected.reason === "http-non-2xx"
|
|
211
|
-
&& projected.httpStatus === 404
|
|
212
|
-
) {
|
|
213
|
-
return false;
|
|
214
|
-
}
|
|
215
|
-
if (projected.status === "unavailable" && projected.reason === "pull-request") {
|
|
216
|
-
throw new DiaristTicketResolutionError(
|
|
217
|
-
"ticket-is-pull-request",
|
|
218
|
-
`ticket #${input.ticketNumber} resolves to a pull request, not an issue`,
|
|
219
|
-
{ cause: projected },
|
|
220
|
-
);
|
|
221
|
-
}
|
|
222
|
-
if (projected.status === "unavailable") {
|
|
223
|
-
throw new DiaristTicketResolutionError(
|
|
224
|
-
"ticket-unavailable",
|
|
225
|
-
`ticket verification unavailable for ${input.owner}/${input.repo}#${input.ticketNumber}`,
|
|
226
|
-
{ cause: projected },
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
throw new DiaristTicketResolutionError(
|
|
230
|
-
"ticket-invalid",
|
|
231
|
-
`ticket verification returned an invalid issue payload for ${input.owner}/${input.repo}#${input.ticketNumber}`,
|
|
232
|
-
{ cause: projected },
|
|
233
|
-
);
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/** Mechanical verify after LLM assertion. true-unbound passes; ticket needs both checks. */
|
|
238
|
-
export async function verifyDiaristTicketAssertion(input: {
|
|
239
|
-
readonly assertion: DiaristTicketAssertion;
|
|
240
|
-
readonly instruction: string;
|
|
241
|
-
readonly origin: { readonly owner: string; readonly repo: string } | undefined;
|
|
242
|
-
readonly checkExistence: TicketExistenceChecker;
|
|
243
|
-
readonly signal?: AbortSignal;
|
|
244
|
-
}): Promise<DiaristTicketResolution> {
|
|
245
|
-
if (input.assertion.kind === "true-unbound") return input.assertion;
|
|
246
|
-
const n = input.assertion.ticketNumber;
|
|
247
|
-
if (!instructionContainsTicketNumber(input.instruction, n)) {
|
|
248
|
-
throw new DiaristTicketResolutionError(
|
|
249
|
-
"number-not-in-instruction",
|
|
250
|
-
`diarist ticket assertion #${n} complete decimal number does not appear in accepted instruction`,
|
|
251
|
-
);
|
|
252
|
-
}
|
|
253
|
-
if (input.origin === undefined) {
|
|
254
|
-
throw new DiaristTicketResolutionError(
|
|
255
|
-
"origin-unresolved",
|
|
256
|
-
`diarist ticket assertion #${n} requires a resolvable github.com origin remote`,
|
|
257
|
-
);
|
|
258
|
-
}
|
|
259
|
-
let exists: boolean;
|
|
260
|
-
try {
|
|
261
|
-
exists = await input.checkExistence({
|
|
262
|
-
owner: input.origin.owner,
|
|
263
|
-
repo: input.origin.repo,
|
|
264
|
-
ticketNumber: n,
|
|
265
|
-
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
266
|
-
});
|
|
267
|
-
} catch (error) {
|
|
268
|
-
if (error instanceof DiaristTicketResolutionError) throw error;
|
|
269
|
-
throw new DiaristTicketResolutionError(
|
|
270
|
-
"ticket-unavailable",
|
|
271
|
-
`diarist ticket assertion #${n} live verification failed`,
|
|
272
|
-
{ cause: error },
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
if (!exists) {
|
|
276
|
-
throw new DiaristTicketResolutionError(
|
|
277
|
-
"ticket-missing",
|
|
278
|
-
`diarist ticket assertion #${n} does not exist as a live issue on ${input.origin.owner}/${input.origin.repo}`,
|
|
279
|
-
);
|
|
280
|
-
}
|
|
281
|
-
return input.assertion;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
export async function resolveDiaristTicketFromInstruction(input: {
|
|
285
|
-
readonly instruction: string;
|
|
286
|
-
readonly origin: { readonly owner: string; readonly repo: string } | undefined;
|
|
287
|
-
readonly resolver: DiaristTicketResolver;
|
|
288
|
-
readonly checkExistence: TicketExistenceChecker;
|
|
289
|
-
readonly signal?: AbortSignal;
|
|
290
|
-
}): Promise<DiaristTicketResolution> {
|
|
291
|
-
const assertion = await input.resolver({
|
|
292
|
-
instruction: input.instruction,
|
|
293
|
-
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
294
|
-
});
|
|
295
|
-
return verifyDiaristTicketAssertion({
|
|
296
|
-
assertion,
|
|
297
|
-
instruction: input.instruction,
|
|
298
|
-
origin: input.origin,
|
|
299
|
-
checkExistence: input.checkExistence,
|
|
300
|
-
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
301
|
-
});
|
|
302
|
-
}
|