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.
@@ -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.14-worker-context-progress-v1";
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;
@@ -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.14-worker-context-progress-v1";
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 [];
@@ -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
- const phase = mission.review?.child ? "verification" : "initial";
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
- return undefined;
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
- if (tokens !== undefined)
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
- if (tokens !== undefined)
306
- observed.modelUsage[modelKey].cost += reportedCost;
336
+ modelUsage.cost += reportedCost;
307
337
  }
308
338
  }
309
339
  }
310
340
  }
311
341
  catch {
312
- return undefined;
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
- `使用量 ${metricText(result.cost.total_tokens, (value) => `${value.toLocaleString("ja-JP")} tokens`)}`,
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 = observation.complete;
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
- else {
166
- totals.set(model, (totals.get(model) ?? 0) + tokens);
167
- const observed = session.modelUsage?.[model];
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 !== 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: usageComplete && total > 0 ? [...totals].sort(([a], [b]) => a.localeCompare(b)).map(([model, tokens]) => {
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
- ...(mix === null ? ["モデル内訳 usage未取得"] : visible.map((entry) => {
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
- export function owningServiceSessionList() {
2
+ function owningServiceClient() {
3
3
  let pending;
4
- async function connect() {
5
- const [{ Service }, { OpenCode }] = await Promise.all([import("@opencode/client/service"), import("@opencode/client")]);
6
- // Discovery is read-only: never start, stop, or replace the user's server. A private server
7
- // must not silently read the shared server's database, even if both contain the same IDs.
8
- const endpoint = await Service.discover();
9
- if (!endpoint)
10
- throw new Error("v2-history-owning-service-unavailable");
11
- const client = OpenCode.make({ baseUrl: endpoint.url, headers: Service.headers(endpoint) });
12
- const info = await client.server.info({ signal: AbortSignal.timeout(10_000) });
13
- if (info.pid !== process.pid)
14
- throw new Error("v2-history-owning-service-unavailable");
15
- return client;
16
- }
17
- return async (input) => {
18
- const current = pending ??= connect();
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
- context.message ? nativePages(cursor => context.message.list({ sessionID: id, limit: 100, ...(cursor ? { cursor } : { order: "asc" }) }))
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sortie-dogs",
3
- "version": "0.12.14",
3
+ "version": "0.12.15",
4
4
  "description": "Bounded agent harness and validated orchestration loop plugin for OpenCode",
5
5
  "keywords": [
6
6
  "opencode",