sortie-dogs 0.12.13 → 0.12.14

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.13-mission-cold-recovery-v1";
6
+ export declare const V010_RUNTIME_ASSET_VERSION = "0.12.14-worker-context-progress-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.13-mission-cold-recovery-v1";
6
+ export const V010_RUNTIME_ASSET_VERSION = "0.12.14-worker-context-progress-v1";
@@ -358,6 +358,8 @@ export function missionPacket(mission, run) {
358
358
  ...(run ? { run_id: run.runID, status: run.phase, decision: run.decision,
359
359
  units: run.units.map(unit => ({ id: unit.unit.id, title: unit.unit.title, status: unit.status,
360
360
  child_session_id: unit.childSessionID, result_class: unit.resultClass, evidence: unit.evidence,
361
+ handoff_path: unit.handoffPath, operation_manifest_path: unit.manifestPath,
362
+ scope_read: unit.unit.read, scope_write: unit.unit.write, validation: unit.unit.validation,
361
363
  ...(unit.dispatchDenial ? { dispatch_denial: unit.dispatchDenial } : {}) })) } : {}),
362
364
  next_action: mission.phase === "completed" ? "Mission completed. Report the accepted result and retained review gaps; no further dispatch or completion call is needed."
363
365
  : mission.phase === "submitted" && mission.submission?.status === "ready"
@@ -340,6 +340,8 @@ export declare class OperatorRuntime {
340
340
  completionGoalFingerprint(state: OperatorState): Promise<string>;
341
341
  verifyContinuityControls(state: OperatorState): Promise<void>;
342
342
  packet(state: OperatorState): unknown;
343
+ /** Reconstruct the admitted child's instructions without trusting a lossy conversation summary. */
344
+ workerContext(root: string, child: string): Promise<string | undefined>;
343
345
  continuationCheckpoint(root: string): Promise<string | undefined>;
344
346
  private verifyControls;
345
347
  /** Follow only the host's authorized parent-link repair; keep the durable control pin in sync. */
@@ -1919,6 +1919,26 @@ export class OperatorRuntime {
1919
1919
  throw new Error("operator-packet-too-large-no-evidence-truncated");
1920
1920
  return packet;
1921
1921
  }
1922
+ /** Reconstruct the admitted child's instructions without trusting a lossy conversation summary. */
1923
+ async workerContext(root, child) {
1924
+ const state = await this.read(root);
1925
+ if (!state || state.phase !== "running")
1926
+ return undefined;
1927
+ const unit = state.units.find(item => item.childSessionID === child && item.status === "running");
1928
+ if (!unit)
1929
+ return undefined;
1930
+ // Keep this prefix stable across reads/edits: changing counters belong in status/tool results.
1931
+ return `SORTIE_WORKER_CONTEXT\n${JSON.stringify({ root_session_id: root, run_id: state.runID,
1932
+ unit_id: unit.unit.id, task_id: /^task_id: (.+)$/m.exec(unit.task.prompt)?.[1],
1933
+ project_root: this.projectRoot, handoff_path: unit.handoffPath, operation_manifest: unit.manifestPath,
1934
+ objective: unit.unit.objective, read: unit.unit.read, write: unit.unit.write,
1935
+ validation: unit.unit.validation, acceptance: state.acceptance,
1936
+ ...(unit.repairValidation === null ? {} : { validation_only: true }),
1937
+ })}\nThis is the durable assignment for this same Worker, including after compaction or reload. ` +
1938
+ "A missing path in a summary does not mean the handoff is missing. Read the retained handoff/manifest and inspect the actual diff and outputs before claiming no edits or replaying work. " +
1939
+ `Use ${this.profile.toolPrefix}operator_status for observed validation and remaining work; do not repeat successful checks for an unchanged candidate. ` +
1940
+ "This context is an assignment, not proof of execution or acceptance.";
1941
+ }
1922
1942
  async continuationCheckpoint(root) {
1923
1943
  const state = await this.read(root);
1924
1944
  const cancelledRemediation = state?.phase === "cancelled" &&
@@ -1,5 +1,6 @@
1
- /** Display-only native Task progress. Never prompts a model or changes execution ownership. */
2
1
  type Sink = (value: Record<string, unknown>) => Promise<void>;
3
2
  export declare function bindMissionProgress(root: string, sink: Sink): () => void;
4
3
  export declare function publishMissionProgress(root: string, value: Record<string, unknown>): Promise<void>;
4
+ /** Read durable transitions even when settlement ran in a different/reloaded plugin instance. */
5
+ export declare function missionProgressReader(directory: string, root: string, callID: string): () => Promise<Record<string, unknown> | undefined>;
5
6
  export {};
@@ -1,3 +1,7 @@
1
+ /** Display-only native Task progress. Never prompts a model or changes execution ownership. */
2
+ import { OperatorMissionRuntime } from "../core/operator-mission.js";
3
+ import { OperatorRuntime } from "../core/operator-runtime.js";
4
+ import { V010_RUNTIME_PROFILE } from "../core/runtime-profile.js";
1
5
  const sinks = new Map();
2
6
  export function bindMissionProgress(root, sink) {
3
7
  sinks.set(root, sink);
@@ -7,3 +11,27 @@ export function bindMissionProgress(root, sink) {
7
11
  export async function publishMissionProgress(root, value) {
8
12
  await sinks.get(root)?.(value).catch(() => undefined);
9
13
  }
14
+ /** Read durable transitions even when settlement ran in a different/reloaded plugin instance. */
15
+ export function missionProgressReader(directory, root, callID) {
16
+ const missions = new OperatorMissionRuntime(directory, V010_RUNTIME_PROFILE);
17
+ return async () => {
18
+ const mission = await missions.read(root);
19
+ if (!mission || mission.callID !== callID)
20
+ return undefined;
21
+ // OperatorRuntime caches its own writes; a display observer must read a fresh disk snapshot.
22
+ const run = await new OperatorRuntime(directory, V010_RUNTIME_PROFILE).read(root);
23
+ const units = run?.runID === mission.runID ? run.units : [];
24
+ const running = units.find(unit => unit.status === "running");
25
+ const last = mission.progress.at(-1);
26
+ const status = running ? "running" : last?.status ?? mission.phase;
27
+ const title = running?.unit.title ?? last?.title ?? mission.requirements[0]?.text ?? "Coordinator";
28
+ return { description: `🐾 ${status}: ${title}`, sortie_progress: {
29
+ mission_id: mission.id, phase: mission.phase, unit: running ? `${run.runID}/${running.unit.id}` : last?.unit ?? null,
30
+ title, status, child_session_id: running?.childSessionID ?? null,
31
+ completed_units: units.filter(unit => unit.status === "succeeded").length,
32
+ failed_units: units.filter(unit => unit.status === "failed").length,
33
+ historical_failed_attempts: mission.progress.filter(unit => unit.status === "failed").length,
34
+ accepted: mission.phase === "completed",
35
+ } };
36
+ };
37
+ }
@@ -2112,6 +2112,11 @@ export function createProfiledPlugin(profile, assetVersion) {
2112
2112
  (output.system ??= []).push(`SORTIE_RUNTIME_PROFILE ${profile.id}; marker ${assetVersion}. ` +
2113
2113
  `Shared MkII protocol role names are logical: ${protocolMap}. Use only ${profile.toolPrefix} tools for this profile. ` +
2114
2114
  "Never rewrite user acceptance or evidence to rename protocol roles. Final acceptance belongs only to the root coordinator.");
2115
+ if ((await identity(request.sessionID)).role === "dog-worker") {
2116
+ const context = await operators.workerContext(root, request.sessionID);
2117
+ if (context)
2118
+ (output.system ??= []).push(context);
2119
+ }
2115
2120
  const proposal = await proposals.read(root);
2116
2121
  if (proposal?.phase !== "approved" && proposal?.proposal_session_id === request.sessionID) {
2117
2122
  // Only immutable identity and the frozen budget caps belong here. Consumed counters move with
@@ -2166,6 +2171,12 @@ export function createProfiledPlugin(profile, assetVersion) {
2166
2171
  const root = await rootFor(request.sessionID);
2167
2172
  if (!root)
2168
2173
  return;
2174
+ if ((await identity(request.sessionID)).role === "dog-worker") {
2175
+ const context = await operators.workerContext(root, request.sessionID);
2176
+ if (context)
2177
+ (output.context ??= []).push(context +
2178
+ " Preserve the actual edits, tool outcomes and next action in the summary; do not replace them with an empty-work claim.");
2179
+ }
2169
2180
  if ((await identity(request.sessionID)).role === "dog-operator") {
2170
2181
  const mission = await missions.read(root);
2171
2182
  if (mission?.coordinator === request.sessionID) {
package/dist/plugin/v2.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { SortieDogsV010Plugin } from "./profiled.js";
2
- import { bindMissionProgress } from "./mission-progress.js";
2
+ import { bindMissionProgress, missionProgressReader } from "./mission-progress.js";
3
3
  import { owningServiceSessionList } from "./v2-session-history.js";
4
4
  import { createHash } from "node:crypto";
5
5
  import { readFileSync } from "node:fs";
@@ -332,18 +332,50 @@ async function registerV2Hooks(context, hooks) {
332
332
  return execute(value, execution);
333
333
  const report = execution.progress;
334
334
  let metadata = {};
335
- const unbind = bindMissionProgress(String(execution.sessionID), async (progress) => {
336
- metadata = { ...metadata, ...progress };
337
- await report(metadata);
338
- });
335
+ let pending = Promise.resolve();
336
+ const update = (update) => {
337
+ metadata = { ...metadata, ...update };
338
+ const snapshot = { ...metadata };
339
+ pending = pending.catch(() => undefined).then(() => report(snapshot));
340
+ return pending;
341
+ };
342
+ const unbind = bindMissionProgress(String(execution.sessionID), update);
343
+ const readProgress = missionProgressReader(context.location.directory, String(execution.sessionID), String(execution.id));
344
+ let previous = "", reading;
345
+ const refresh = () => {
346
+ if (reading)
347
+ return reading;
348
+ reading = (async () => {
349
+ try {
350
+ const progress = await readProgress(), key = JSON.stringify(progress);
351
+ if (progress && key !== previous) {
352
+ await update(progress);
353
+ previous = key;
354
+ }
355
+ }
356
+ catch (error) {
357
+ console.warn("[sortie-dogs-v010] mission progress unavailable", error instanceof Error ? error.message : "unknown");
358
+ }
359
+ })().finally(() => { reading = undefined; });
360
+ return reading;
361
+ };
362
+ // The native executor is a stable snapshot. Poll only local durable state, not models or
363
+ // session status APIs; this also recovers progress after another plugin instance settles work.
364
+ const timer = setInterval(() => { void refresh(); }, 1000);
365
+ timer.unref();
339
366
  try {
340
- return await execute(value, { ...execution, progress: async (update) => {
341
- metadata = { ...metadata, ...update };
342
- await report(metadata);
343
- } });
367
+ await refresh();
368
+ const result = await execute(value, { ...execution, progress: update });
369
+ await refresh();
370
+ await pending.catch(() => undefined);
371
+ // Native completion replaces metadata, so retain our display fields alongside its own.
372
+ return { ...result, metadata: { ...metadata, ...(record(result.metadata) ? result.metadata : {}) } };
344
373
  }
345
374
  finally {
375
+ clearInterval(timer);
346
376
  unbind();
377
+ await reading;
378
+ await pending.catch(() => undefined);
347
379
  }
348
380
  };
349
381
  });
@@ -117,6 +117,11 @@ You are Coordinator. Own most of the practical work and dispatch within the save
117
117
  Read/search and confirmation shell commands are available; source edits belong to Worker. Do not edit
118
118
  through shell. There is no proposal/approval/contract-repair round trip in this route.
119
119
 
120
+ For release/benchmark work, resolve the package from its release receipt and hash, not a matching version
121
+ or filename. Record the runner's own revision separately. Before repairing infrastructure on an old branch,
122
+ check current main for an existing fix; preserve local edits and use a current-main worktree when needed.
123
+ Report setup/route failures as such, with observed inference count, instead of calling runner exits a score.
124
+
120
125
  Investigate only enough to start the first useful Worker. Prefer a targeted read/reproduction over a broad
121
126
  inventory or speculative full design. Call ${profile.toolPrefix}plan_units with concise units:
122
127
  title, objective, read/write file or directory scopes, validation commands, optionally requirement_ids.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sortie-dogs",
3
- "version": "0.12.13",
3
+ "version": "0.12.14",
4
4
  "description": "Bounded agent harness and validated orchestration loop plugin for OpenCode",
5
5
  "keywords": [
6
6
  "opencode",