sortie-dogs 0.12.14 → 0.12.15
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/asset-version.d.ts +1 -1
- package/dist/asset-version.js +1 -1
- package/dist/core/operator-mission.d.ts +2 -0
- package/dist/plugin/mission-review.d.ts +2 -0
- package/dist/plugin/mission-review.js +10 -0
- package/dist/plugin/profiled.js +21 -3
- package/dist/plugin/run-metrics.js +47 -11
- package/dist/plugin/sortie-debrief.d.ts +5 -0
- package/dist/plugin/sortie-debrief.js +15 -8
- package/dist/plugin/v2-session-history.d.ts +3 -2
- package/dist/plugin/v2-session-history.js +27 -17
- package/dist/plugin/v2.js +9 -3
- package/package.json +1 -1
package/dist/asset-version.d.ts
CHANGED
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
5
|
export declare const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
|
|
6
|
-
export declare const V010_RUNTIME_ASSET_VERSION = "0.12.
|
|
6
|
+
export declare const V010_RUNTIME_ASSET_VERSION = "0.12.15-review-lineage-model-gauges-v1";
|
|
7
7
|
export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION | typeof V010_RUNTIME_ASSET_VERSION;
|
package/dist/asset-version.js
CHANGED
|
@@ -3,4 +3,4 @@
|
|
|
3
3
|
* installed project marker without importing every asset body.
|
|
4
4
|
*/
|
|
5
5
|
export const RUNTIME_ASSET_VERSION = "0.3.89-completion-proof-v1";
|
|
6
|
-
export const V010_RUNTIME_ASSET_VERSION = "0.12.
|
|
6
|
+
export const V010_RUNTIME_ASSET_VERSION = "0.12.15-review-lineage-model-gauges-v1";
|
|
@@ -46,6 +46,8 @@ export interface OperatorMission {
|
|
|
46
46
|
verdict: "pending" | "PASS" | "findings" | "evidence-gaps" | "skipped-low-risk";
|
|
47
47
|
result?: string;
|
|
48
48
|
child?: string;
|
|
49
|
+
/** Completed, independent initial review for this mission, not merely an inherited child ID. */
|
|
50
|
+
initialPrompt?: string;
|
|
49
51
|
/** Reviews on this mission that found only missing evidence; bounded by MISSION_EVIDENCE_GAP_REVIEW_LIMIT. */
|
|
50
52
|
evidenceGapReviews?: number;
|
|
51
53
|
};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { OperatorState } from "../core/operator-runtime.js";
|
|
2
2
|
import { type OperatorMission } from "../core/operator-mission.js";
|
|
3
3
|
import { type RuntimeProfile } from "../core/runtime-profile.js";
|
|
4
|
+
/** A child ID alone cannot establish the initial phase after a restart or failed dispatch. */
|
|
5
|
+
export declare function initialMissionReviewPrompt(mission: OperatorMission, prompts: readonly string[]): string | undefined;
|
|
4
6
|
/** Recover from native completion records, never from the pending review's inherited child ID. */
|
|
5
7
|
export declare function completedMissionReviewPrompts(mission: OperatorMission | undefined, profile: RuntimeProfile, root: string, requestedPrompt: string, host: {
|
|
6
8
|
get(id: string): Promise<unknown>;
|
|
@@ -12,11 +12,21 @@ import { normalizeManifestScope } from "../core/path.js";
|
|
|
12
12
|
import { declaredArtifacts } from "./declared-artifacts.js";
|
|
13
13
|
const exec = promisify(execFile);
|
|
14
14
|
const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
15
|
+
/** A child ID alone cannot establish the initial phase after a restart or failed dispatch. */
|
|
16
|
+
export function initialMissionReviewPrompt(mission, prompts) {
|
|
17
|
+
return [mission.review?.initialPrompt, ...prompts].find(prompt => typeof prompt === "string" &&
|
|
18
|
+
prompt.startsWith(`candidate_id: ${mission.id}\n`) && /^review_phase: initial$/mu.test(prompt) &&
|
|
19
|
+
/^canonical_validation_exit: 0$/mu.test(prompt));
|
|
20
|
+
}
|
|
15
21
|
/** Recover from native completion records, never from the pending review's inherited child ID. */
|
|
16
22
|
export async function completedMissionReviewPrompts(mission, profile, root, requestedPrompt, host) {
|
|
17
23
|
if (!mission || mission.root !== root || !mission.coordinator || ["completed", "cancelled"].includes(mission.phase) ||
|
|
18
24
|
mission.review?.task?.prompt !== requestedPrompt)
|
|
19
25
|
return [];
|
|
26
|
+
// The completion hook has already bound this prompt to a real independent Reviewer child.
|
|
27
|
+
// Avoid a second V2 history read when its page/list API is temporarily unavailable.
|
|
28
|
+
if (initialMissionReviewPrompt(mission, []))
|
|
29
|
+
return [mission.review.initialPrompt];
|
|
20
30
|
const coordinator = await host.get(mission.coordinator);
|
|
21
31
|
if (!record(coordinator) || coordinator.parentID !== root || canonicalAgent(profile, coordinator.agent) !== "dog-operator")
|
|
22
32
|
return [];
|
package/dist/plugin/profiled.js
CHANGED
|
@@ -14,7 +14,7 @@ import { normalizeCommand } from "./gate.js";
|
|
|
14
14
|
import { normalizeRelativePath } from "../core/path.js";
|
|
15
15
|
import { MISSION_EVIDENCE_GAP_REVIEW_LIMIT, OperatorMissionRuntime, missionPacket, missionPlan, missionReviewAccepted, missionReviewTask, missionReviewTraces, missionReviewVerdict } from "../core/operator-mission.js";
|
|
16
16
|
import { publishMissionProgress } from "./mission-progress.js";
|
|
17
|
-
import { completedMissionReviewPrompts, missionReviewSource } from "./mission-review.js";
|
|
17
|
+
import { completedMissionReviewPrompts, initialMissionReviewPrompt, missionReviewSource } from "./mission-review.js";
|
|
18
18
|
import { missionLocations, missionLocationPacket } from "./mission-location.js";
|
|
19
19
|
import { SOURCE_REVIEW_RISK_TAGS } from "../core/consultation.js";
|
|
20
20
|
import { createHash } from "node:crypto";
|
|
@@ -184,6 +184,10 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
184
184
|
const result = payload(await session("messages", { path: { id }, query: { directory: input.directory } }));
|
|
185
185
|
return Array.isArray(result) ? result.filter(record) : [];
|
|
186
186
|
}
|
|
187
|
+
async function reviewMessages(id) {
|
|
188
|
+
const result = payload(await session(typeof nativeSession?.reviewMessages === "function" ? "reviewMessages" : "messages", { path: { id }, query: { directory: input.directory } }));
|
|
189
|
+
return Array.isArray(result) ? result.filter(record) : [];
|
|
190
|
+
}
|
|
187
191
|
async function identity(id) {
|
|
188
192
|
const info = payload(await session("get", { path: { id }, query: { directory: input.directory } }));
|
|
189
193
|
const parent = record(info) && typeof info.parentID === "string" ? info.parentID : undefined;
|
|
@@ -334,7 +338,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
334
338
|
},
|
|
335
339
|
completedReviewPrompts: async (root, prompt) => completedMissionReviewPrompts(await missions.read(root), profile, root, prompt, {
|
|
336
340
|
get: async (id) => payload(await session("get", { path: { id }, query: { directory: input.directory } })),
|
|
337
|
-
messages,
|
|
341
|
+
messages: reviewMessages,
|
|
338
342
|
}),
|
|
339
343
|
requiresExplicitAcceptance: async (root) => {
|
|
340
344
|
const mission = await missions.read(root);
|
|
@@ -1418,7 +1422,17 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1418
1422
|
next_action: missionReviewAccepted(mission.review) ? "Review is already recorded for this unchanged candidate. Submit ready with any remaining gaps; do not repeat review."
|
|
1419
1423
|
: "Review is already recorded for this unchanged candidate. Address its findings or supply new evidence through traces; do not repeat the same review." });
|
|
1420
1424
|
}
|
|
1421
|
-
|
|
1425
|
+
// An old child can belong to a previous run, while the fast-lane is reset on a new
|
|
1426
|
+
// turn. Generate verification only when a completed initial review is recoverable;
|
|
1427
|
+
// otherwise generate an admissible initial review instead of trapping the Coordinator.
|
|
1428
|
+
const prior = mission.review?.task?.prompt;
|
|
1429
|
+
const completed = risk.length > 0 && prior && mission.review?.child && !mission.review.initialPrompt
|
|
1430
|
+
? await completedMissionReviewPrompts(mission, profile, root, prior, {
|
|
1431
|
+
get: async (id) => payload(await session("get", { path: { id }, query: { directory: input.directory } })),
|
|
1432
|
+
messages: reviewMessages,
|
|
1433
|
+
}).catch(() => []) : [];
|
|
1434
|
+
const initialPrompt = initialMissionReviewPrompt(mission, completed);
|
|
1435
|
+
const phase = initialPrompt ? "verification" : "initial";
|
|
1422
1436
|
const task = risk.length === 0 ? null : { subagent_type: profileAgent(profile, "dog-reviewer"),
|
|
1423
1437
|
description: `🔎 ${run.units[0].unit.title}`, prompt: [
|
|
1424
1438
|
`candidate_id: ${mission.id}`, `review_phase: ${phase}`, "canonical_validation_exit: 0", `risk_tags: [${risk.join(", ")}]`,
|
|
@@ -1434,6 +1448,7 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1434
1448
|
const reviewed = await missions.update(root, item => {
|
|
1435
1449
|
item.review = { runID: run.runID, risk: risk, source: source.fingerprint, requestFingerprint,
|
|
1436
1450
|
task, verdict: task ? "pending" : "skipped-low-risk", ...(mission.review?.child ? { child: mission.review.child } : {}),
|
|
1451
|
+
...(initialPrompt ? { initialPrompt } : {}),
|
|
1437
1452
|
...(mission.review?.evidenceGapReviews ? { evidenceGapReviews: mission.review.evidenceGapReviews } : {}) };
|
|
1438
1453
|
});
|
|
1439
1454
|
return JSON.stringify(task ? { status: "review-required", task: missionReviewTask(reviewed) } : { status: "skipped-low-risk" });
|
|
@@ -1982,6 +1997,9 @@ export function createProfiledPlugin(profile, assetVersion) {
|
|
|
1982
1997
|
if (mission.review.verdict === "evidence-gaps")
|
|
1983
1998
|
mission.review.evidenceGapReviews = (mission.review.evidenceGapReviews ?? 0) + 1;
|
|
1984
1999
|
mission.review.child = child;
|
|
2000
|
+
if (independent && last && child && mission.review.task?.prompt.startsWith(`candidate_id: ${mission.id}\nreview_phase: initial\n`)) {
|
|
2001
|
+
mission.review.initialPrompt = mission.review.task.prompt;
|
|
2002
|
+
}
|
|
1985
2003
|
}
|
|
1986
2004
|
});
|
|
1987
2005
|
if (reviewed.review?.verdict === "evidence-gaps" && typeof output.output === "string") {
|
|
@@ -206,8 +206,38 @@ export async function collectRunMetrics(client, rootSessionID, directory, now =
|
|
|
206
206
|
for (const id of ids) {
|
|
207
207
|
try {
|
|
208
208
|
const messages = assistantMessages(await session.messages.call(session, { path: { id }, query: { directory } }));
|
|
209
|
-
if (messages === undefined)
|
|
210
|
-
|
|
209
|
+
if (messages === undefined) {
|
|
210
|
+
messagesComplete = false;
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
if (!hierarchyComplete && session.get !== undefined) {
|
|
214
|
+
// Some V2 plugin facades cannot list children. Recover recorded native dispatches
|
|
215
|
+
// during this same history pass, without rerunning work or discarding parent usage.
|
|
216
|
+
const recorded = new Set();
|
|
217
|
+
for (const message of messages)
|
|
218
|
+
for (const raw of Array.isArray(message.parts) ? message.parts : []) {
|
|
219
|
+
const part = record(raw), state = record(part?.state), metadata = record(state?.metadata);
|
|
220
|
+
if (part?.type !== "tool" || part.tool !== "task")
|
|
221
|
+
continue;
|
|
222
|
+
const input = record(state?.input);
|
|
223
|
+
for (const child of [metadata?.sessionId, metadata?.sessionID, input?.task_id]) {
|
|
224
|
+
if (typeof child === "string" && child && !visited.has(child))
|
|
225
|
+
recorded.add(child);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
for (const child of recorded) {
|
|
229
|
+
if (ids.length >= MAX_SESSIONS)
|
|
230
|
+
break;
|
|
231
|
+
try {
|
|
232
|
+
const info = record(unwrap(await session.get.call(session, { path: { id: child }, query: { directory } })));
|
|
233
|
+
if (info?.parentID !== id)
|
|
234
|
+
continue;
|
|
235
|
+
visited.add(child);
|
|
236
|
+
ids.push(child);
|
|
237
|
+
}
|
|
238
|
+
catch { /* Keep this partial display; unobserved usage is never zero-filled. */ }
|
|
239
|
+
}
|
|
240
|
+
}
|
|
211
241
|
const observed = observeDebriefSession(id, id === rootSessionID, messages, window === undefined ? undefined : { start: windowStart, end: windowEnd });
|
|
212
242
|
observedSessions.push(observed);
|
|
213
243
|
for (const message of messages) {
|
|
@@ -257,9 +287,9 @@ export async function collectRunMetrics(client, rootSessionID, directory, now =
|
|
|
257
287
|
const modelKey = typeof provider === "string" && typeof model === "string" ? `${provider}/${model}` : "未分類";
|
|
258
288
|
observed.models[modelKey] = tokens === undefined || observed.models[modelKey] === null ? null
|
|
259
289
|
: (observed.models[modelKey] ?? 0) + tokens.total;
|
|
290
|
+
const modelUsage = observed.modelUsage[modelKey] ??= { tokens: 0, uncachedInputTokens: 0, cacheReadTokens: 0,
|
|
291
|
+
cacheWriteTokens: 0, cost: 0, costAvailable: true, estimatedCost: 0, pricedRequests: 0, unpricedRequests: 0 };
|
|
260
292
|
if (tokens !== undefined) {
|
|
261
|
-
const modelUsage = observed.modelUsage[modelKey] ?? { tokens: 0, uncachedInputTokens: 0, cacheReadTokens: 0,
|
|
262
|
-
cacheWriteTokens: 0, cost: 0, costAvailable: true, estimatedCost: 0, pricedRequests: 0, unpricedRequests: 0 };
|
|
263
293
|
modelUsage.tokens += tokens.total;
|
|
264
294
|
modelUsage.uncachedInputTokens += tokens.input;
|
|
265
295
|
modelUsage.cacheReadTokens += tokens.cacheRead;
|
|
@@ -289,29 +319,31 @@ export async function collectRunMetrics(client, rootSessionID, directory, now =
|
|
|
289
319
|
role.cacheReadTokens += tokens.cacheRead;
|
|
290
320
|
role.cacheWriteTokens += tokens.cacheWrite;
|
|
291
321
|
}
|
|
292
|
-
else
|
|
322
|
+
else {
|
|
293
323
|
tokensAvailable = false;
|
|
324
|
+
modelUsage.unpricedRequests = (modelUsage.unpricedRequests ?? 0) + 1;
|
|
325
|
+
}
|
|
294
326
|
const usageInfo = record(usage.value.info) ?? usage.value;
|
|
295
327
|
const reportedCost = number(usageInfo.cost) ?? number(usage.value.cost);
|
|
296
328
|
if (reportedCost === undefined) {
|
|
297
329
|
costAvailable = false;
|
|
298
330
|
role.costAvailable = false;
|
|
299
|
-
|
|
300
|
-
observed.modelUsage[modelKey].costAvailable = false;
|
|
331
|
+
modelUsage.costAvailable = false;
|
|
301
332
|
}
|
|
302
333
|
else {
|
|
303
334
|
cost += reportedCost;
|
|
304
335
|
role.cost += reportedCost;
|
|
305
|
-
|
|
306
|
-
observed.modelUsage[modelKey].cost += reportedCost;
|
|
336
|
+
modelUsage.cost += reportedCost;
|
|
307
337
|
}
|
|
308
338
|
}
|
|
309
339
|
}
|
|
310
340
|
}
|
|
311
341
|
catch {
|
|
312
|
-
|
|
342
|
+
messagesComplete = false;
|
|
313
343
|
}
|
|
314
344
|
}
|
|
345
|
+
if (observedSessions.length === 0)
|
|
346
|
+
return undefined;
|
|
315
347
|
let created;
|
|
316
348
|
if (session.get !== undefined) {
|
|
317
349
|
try {
|
|
@@ -388,6 +420,10 @@ export function formatSortieResult(result, presentation = {}) {
|
|
|
388
420
|
const estimatedCost = estimate === undefined ? "計測不可"
|
|
389
421
|
: estimate.pricedRequests === 0 ? "未換算"
|
|
390
422
|
: `$${estimate.usd.toFixed(4)}${estimate.unpricedRequests > 0 ? "(一部未換算)" : ""}`;
|
|
423
|
+
const measuredTokens = result.debrief?.mixCoverage?.observedTokens;
|
|
424
|
+
const tokenText = result.cost.total_tokens.availability === "unavailable" && measuredTokens !== undefined && measuredTokens > 0
|
|
425
|
+
? `${measuredTokens.toLocaleString("ja-JP")} tokens(観測分)`
|
|
426
|
+
: metricText(result.cost.total_tokens, (value) => `${value.toLocaleString("ja-JP")} tokens`);
|
|
391
427
|
const body = [
|
|
392
428
|
"🐾 SORTIE DOGS — 帰還報告",
|
|
393
429
|
result.result_id[0],
|
|
@@ -411,7 +447,7 @@ export function formatSortieResult(result, presentation = {}) {
|
|
|
411
447
|
displayText(presentation.next),
|
|
412
448
|
"",
|
|
413
449
|
"🪙 COST / PACK",
|
|
414
|
-
`使用量 ${
|
|
450
|
+
`使用量 ${tokenText}`,
|
|
415
451
|
`予測費用 ${estimatedCost} ※予測概算`,
|
|
416
452
|
"",
|
|
417
453
|
...renderDebrief(result.debrief),
|
|
@@ -58,6 +58,11 @@ export interface Debrief {
|
|
|
58
58
|
};
|
|
59
59
|
readonly hostCostZero?: boolean;
|
|
60
60
|
}[] | null;
|
|
61
|
+
/** Shares use observed tokens; incomplete history must not erase known model usage. */
|
|
62
|
+
readonly mixCoverage?: {
|
|
63
|
+
readonly complete: boolean;
|
|
64
|
+
readonly observedTokens: number;
|
|
65
|
+
};
|
|
61
66
|
readonly estimatedCost?: {
|
|
62
67
|
readonly usd: number;
|
|
63
68
|
readonly pricedRequests: number;
|
|
@@ -157,18 +157,22 @@ export function buildDebrief(receipt, contract, observation, records) {
|
|
|
157
157
|
(session.spans.length > 0 || Object.keys(session.models).length > 0));
|
|
158
158
|
const counts = new Map(), totals = new Map();
|
|
159
159
|
const modelUsage = new Map();
|
|
160
|
-
let usageComplete =
|
|
160
|
+
let usageComplete = true;
|
|
161
161
|
for (const session of sessions) {
|
|
162
162
|
for (const [model, tokens] of Object.entries(session.models)) {
|
|
163
163
|
if (tokens === null || model === "未分類")
|
|
164
164
|
usageComplete = false;
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
const observed = session.modelUsage?.[model];
|
|
166
|
+
// A missing request leaves models[model] null. The independent accumulator still
|
|
167
|
+
// contains real usage from this model's other requests, including older snapshots.
|
|
168
|
+
const measured = tokens ?? observed?.tokens;
|
|
169
|
+
if (measured !== undefined) {
|
|
170
|
+
if (measured > 0)
|
|
171
|
+
totals.set(model, (totals.get(model) ?? 0) + measured);
|
|
168
172
|
const aggregate = modelUsage.get(model) ?? { uncachedInputTokens: 0, cacheReadTokens: 0, cacheWriteTokens: 0,
|
|
169
173
|
cost: 0, costAvailable: true, estimatedCost: 0, pricedRequests: 0, unpricedRequests: 0,
|
|
170
174
|
pricingComplete: true, complete: true };
|
|
171
|
-
if (observed === undefined || observed.tokens !==
|
|
175
|
+
if (observed === undefined || observed.tokens !== measured)
|
|
172
176
|
aggregate.complete = false;
|
|
173
177
|
else {
|
|
174
178
|
aggregate.uncachedInputTokens += observed.uncachedInputTokens;
|
|
@@ -284,13 +288,14 @@ export function buildDebrief(receipt, contract, observation, records) {
|
|
|
284
288
|
session.mutations.some((edit) => edit.end > entry.at))).at(-1);
|
|
285
289
|
const packComplete = observation.complete && sessions.every(session => session.root || session.timingComplete || Object.keys(session.models).length > 0);
|
|
286
290
|
return { pack: packComplete ? [...counts].sort(([a], [b]) => a.localeCompare(b)).map(([model, count]) => ({ model, count })) : null,
|
|
287
|
-
mix:
|
|
291
|
+
mix: total > 0 ? [...totals].sort(([a], [b]) => a.localeCompare(b)).map(([model, tokens]) => {
|
|
288
292
|
const usage = modelUsage.get(model);
|
|
289
293
|
return { model, tokens, percent: tokens / total * 100,
|
|
290
294
|
...(usage?.complete ? { inputCache: { uncachedInputTokens: usage.uncachedInputTokens,
|
|
291
295
|
cacheReadTokens: usage.cacheReadTokens, cacheWriteTokens: usage.cacheWriteTokens } } : {}),
|
|
292
296
|
...(usage?.complete && usage.costAvailable && tokens > 0 && usage.cost === 0 ? { hostCostZero: true } : {}) };
|
|
293
297
|
}) : null,
|
|
298
|
+
mixCoverage: { complete: observation.complete && usageComplete, observedTokens: total },
|
|
294
299
|
...(pricing.complete && pricing.pricedRequests + pricing.unpricedRequests > 0 ? { estimatedCost: {
|
|
295
300
|
usd: pricing.usd, pricedRequests: pricing.pricedRequests, unpricedRequests: pricing.unpricedRequests
|
|
296
301
|
} } : {}),
|
|
@@ -337,13 +342,15 @@ export function renderDebrief(debrief) {
|
|
|
337
342
|
: value === "WAIVED" ? "免除" : "未記録";
|
|
338
343
|
const counts = new Map(packVisible.map((entry) => [entry.model, entry.count]));
|
|
339
344
|
return [
|
|
340
|
-
|
|
345
|
+
`モデル内訳 ${mix === null ? "usage未取得" : debrief?.mixCoverage?.complete === false
|
|
346
|
+
? "観測済みtoken比率(一部未取得)" : "token比率"}`,
|
|
347
|
+
...visible.map((entry) => {
|
|
341
348
|
const count = counts.get(entry.model);
|
|
342
349
|
const input = entry.inputCache === undefined ? 0 : entry.inputCache.uncachedInputTokens +
|
|
343
350
|
entry.inputCache.cacheReadTokens + entry.inputCache.cacheWriteTokens;
|
|
344
351
|
const cache = entry.inputCache === undefined || input === 0 ? "未取得" : `${(entry.inputCache.cacheReadTokens / input * 100).toFixed(0)}%`;
|
|
345
352
|
return `🐕 ${label(entry.model)} ${gauge(entry.percent)} ${entry.percent.toFixed(1)}% ${entry.tokens.toLocaleString("ja-JP")} tokens${count === undefined ? "" : ` ×${count}`} ↺${cache}${entry.hostCostZero ? "†" : ""}`;
|
|
346
|
-
})
|
|
353
|
+
}),
|
|
347
354
|
...(visible.some((entry) => entry.hostCostZero) ? [" †host費用0計上あり(無料・全体価格の評価ではありません)"] : []),
|
|
348
355
|
`⚡ 実行重複率 ${debrief?.overlap !== undefined && debrief.overlap.wallMilliseconds > 0
|
|
349
356
|
? `${(debrief.overlap.workerMilliseconds / debrief.overlap.wallMilliseconds).toFixed(2)}×`
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
-
import type { SessionListInput } from "@opencode/client";
|
|
2
|
-
/** V2's plugin context can omit session.list even though the public client exposes it. */
|
|
1
|
+
import type { SessionListInput, MessageListInput } from "@opencode/client";
|
|
3
2
|
export declare function owningServiceSessionList(): (input: SessionListInput) => Promise<unknown>;
|
|
3
|
+
/** Read-only paginated history for reviews older than V2's bounded session.context window. */
|
|
4
|
+
export declare function owningServiceMessageList(): (input: MessageListInput) => Promise<unknown>;
|
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
/** V2's plugin context can omit session.list even though the public client exposes it. */
|
|
2
|
-
|
|
2
|
+
function owningServiceClient() {
|
|
3
3
|
let pending;
|
|
4
|
-
async
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const client = await current.catch(error => { if (pending === current)
|
|
4
|
+
return async () => {
|
|
5
|
+
const current = pending ??= (async () => {
|
|
6
|
+
const [{ Service }, { OpenCode }] = await Promise.all([import("@opencode/client/service"), import("@opencode/client")]);
|
|
7
|
+
// Discovery is read-only: never start, stop, or replace the user's server. A private server
|
|
8
|
+
// must not silently read the shared server's database, even if both contain the same IDs.
|
|
9
|
+
const endpoint = await Service.discover();
|
|
10
|
+
if (!endpoint)
|
|
11
|
+
throw new Error("v2-history-owning-service-unavailable");
|
|
12
|
+
const client = OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) });
|
|
13
|
+
const info = await client.server.info({ signal: AbortSignal.timeout(10_000) });
|
|
14
|
+
if (info.pid !== process.pid)
|
|
15
|
+
throw new Error("v2-history-owning-service-unavailable");
|
|
16
|
+
return client;
|
|
17
|
+
})();
|
|
18
|
+
return current.catch(error => { if (pending === current)
|
|
20
19
|
pending = undefined; throw error; });
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function owningServiceSessionList() {
|
|
23
|
+
const connect = owningServiceClient();
|
|
24
|
+
return async (input) => {
|
|
25
|
+
const client = await connect();
|
|
21
26
|
return client.session.list(input, { signal: AbortSignal.timeout(10_000) });
|
|
22
27
|
};
|
|
23
28
|
}
|
|
29
|
+
/** Read-only paginated history for reviews older than V2's bounded session.context window. */
|
|
30
|
+
export function owningServiceMessageList() {
|
|
31
|
+
const connect = owningServiceClient();
|
|
32
|
+
return async (input) => (await connect()).message.list(input, { signal: AbortSignal.timeout(10_000) });
|
|
33
|
+
}
|
package/dist/plugin/v2.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { SortieDogsV010Plugin } from "./profiled.js";
|
|
2
2
|
import { bindMissionProgress, missionProgressReader } from "./mission-progress.js";
|
|
3
|
-
import { owningServiceSessionList } from "./v2-session-history.js";
|
|
3
|
+
import { owningServiceMessageList, owningServiceSessionList } from "./v2-session-history.js";
|
|
4
4
|
import { createHash } from "node:crypto";
|
|
5
5
|
import { readFileSync } from "node:fs";
|
|
6
6
|
import { V010_RUNTIME_ASSET_VERSION } from "../asset-version.js";
|
|
@@ -100,10 +100,11 @@ function legacyMessage(value, session, agent) {
|
|
|
100
100
|
return converted === undefined ? [] : [converted];
|
|
101
101
|
}) };
|
|
102
102
|
}
|
|
103
|
-
async function legacyMessages(context, id) {
|
|
103
|
+
async function legacyMessages(context, id, paginated) {
|
|
104
|
+
const list = paginated ?? (context.message ? (input) => context.message.list(input) : undefined);
|
|
104
105
|
const [info, history] = await Promise.all([
|
|
105
106
|
context.session.get({ sessionID: id }).catch(() => undefined),
|
|
106
|
-
|
|
107
|
+
list ? nativePages(cursor => list({ sessionID: id, limit: 100, ...(cursor ? { cursor } : { order: "asc" }) }))
|
|
107
108
|
: context.session.context({ sessionID: id }),
|
|
108
109
|
]);
|
|
109
110
|
const agent = record(info) ? string(info.agent) : undefined;
|
|
@@ -133,6 +134,7 @@ async function nativePages(fetch) {
|
|
|
133
134
|
function legacyClient(context) {
|
|
134
135
|
const directory = context.location.directory;
|
|
135
136
|
const list = owningServiceSessionList();
|
|
137
|
+
const reviewList = owningServiceMessageList();
|
|
136
138
|
const session = {
|
|
137
139
|
get: async (request) => {
|
|
138
140
|
const id = sessionID(request);
|
|
@@ -142,6 +144,10 @@ function legacyClient(context) {
|
|
|
142
144
|
const id = sessionID(request);
|
|
143
145
|
return { data: id === undefined ? [] : await legacyMessages(context, id) };
|
|
144
146
|
},
|
|
147
|
+
reviewMessages: async (request) => {
|
|
148
|
+
const id = sessionID(request);
|
|
149
|
+
return { data: id === undefined ? [] : await legacyMessages(context, id, context.message ? input => context.message.list(input) : reviewList) };
|
|
150
|
+
},
|
|
145
151
|
message: async (request) => {
|
|
146
152
|
const id = sessionID(request), wanted = messageID(request);
|
|
147
153
|
return { data: id === undefined || wanted === undefined ? undefined
|