sortie-dogs 0.13.19 → 0.13.21

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/plugin/v2.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { toolSchema, legacyToolArgs } from "./tool-schema.js";
2
2
  import { SortieDogsV010Plugin } from "./profiled.js";
3
3
  import { bindMissionProgress, missionProgressReader } from "./mission-progress.js";
4
- import { cachedWorkProgress, workRootForActor } from "../core/work-quantity-observations.js";
4
+ import { cachedWorkProgress, forgetWorkSession, retainWorkDirectory, workRootForActor } from "../core/work-quantity-observations.js";
5
5
  import { formatWorkQuantity } from "../core/work-quantity.js";
6
6
  import { owningServiceMessageList, owningServiceSessionList, owningServiceSessionActive } from "./v2-session-history.js";
7
7
  import { nativeContractReadSnapshot } from "./native-contract-read.js";
@@ -870,90 +870,103 @@ export function createSortieDogsV2Plugin(legacyFactory = SortieDogsV010Plugin) {
870
870
  return {
871
871
  id: "sortie-dogs.v010",
872
872
  async setup(context) {
873
- let hooks;
874
- const liveState = new Map();
875
- const background = new NativeBackgroundLifecycle(context, async (dispatch, text) => {
876
- if (dispatch.owner)
877
- hooks.backgroundOwner?.(dispatch.callID, dispatch.owner);
878
- const output = { status: dispatch.terminal === "succeeded" ? "completed" : dispatch.terminal === "interrupted" ? "cancelled" : "error",
879
- output: text, metadata: { sessionID: dispatch.child, status: dispatch.terminal } };
880
- await hooks["tool.execute.after"]?.({ tool: "task", sessionID: dispatch.parent, callID: dispatch.callID,
881
- args: legacyToolInput("subagent", dispatch.input) }, output);
882
- if (dispatch.child)
883
- await syncReviewerCorrectionPermissions(context, hooks, dispatch.child);
884
- // Inline Review already has a compact provenance receipt; retain the full diagnostic packet in its ordinary tool route.
885
- try {
886
- const packet = JSON.parse(output.output);
887
- if (record(packet) && record(packet.completion_receipt))
888
- return JSON.stringify(packet.completion_receipt);
889
- }
890
- catch { /* Ordinary native outputs are not necessarily JSON. */ }
891
- return output.output;
892
- }, (callID, restore) => hooks.backgroundOwner?.(callID, restore), (parent, callID, child, promptID) => hooks.backgroundResult?.(parent, callID, child, promptID) ?? Promise.resolve(undefined));
893
- hooks = await legacyFactory({ directory: context.location.directory, client: legacyClient(context),
894
- nativeBackground: background, returnReportTransport: "tool-result",
895
- reviewerCorrectionPermissions: !!context.agent && !!context.storage && !!context.session.switchAgent }, context.options ?? {});
896
- await registerV2Hooks(context, hooks, background, liveState);
897
- // Recreate exact durable correction profiles at plugin setup, before resumed native Steps
898
- // resolve their agent/tool snapshots. Storage.scan is part of Plugin 2.0.18's public domain.
899
- if (context.storage?.scan) {
900
- let after;
901
- do {
902
- const page = await context.storage.scan({ prefix: correctionStoragePrefix, ...(after ? { after } : {}) });
903
- for (const entry of page.entries)
904
- await syncReviewerCorrectionPermissions(context, hooks, entry.key.slice(correctionStoragePrefix.length));
905
- after = page.next;
906
- } while (after);
907
- }
908
- const finalize = createV2ReturnReportFinalizer(context, hooks);
909
- const controller = new AbortController();
910
- const eventOperations = new Map();
911
- const handleEvent = async (event) => {
912
- const data = record(event.data) ? event.data : {};
913
- const id = string(data.sessionID);
914
- if (event.type === "filesystem.changed" && typeof data.file === "string") {
915
- await hooks.event?.({ event: { type: "file.edited", properties: { file: data.file } } });
916
- return;
917
- }
918
- if (id === undefined)
919
- return;
920
- if (String(event.type).startsWith("session.execution.") || event.type === "session.inbox.enqueued" || event.type === "session.idle")
921
- await background.event(event);
922
- if (event.type === "session.deleted") {
923
- await context.storage?.remove?.(childSelectionKey(id));
924
- liveState.delete(id);
925
- await context.storage?.remove?.(liveStateKey(id));
926
- // This native event is definite deletion, unlike a transient session.get failure.
927
- await disposeCorrectionPermissions(context, id);
928
- }
929
- if (event.type === "session.execution.succeeded")
930
- await finalize(id);
931
- if (event.type === "session.created" || event.type === "session.deleted" || event.type === "session.idle") {
932
- const info = event.type === "session.created" ? await context.session.get({ sessionID: id }).catch(() => ({ id })) : { id };
933
- await hooks.event?.({ event: { type: String(event.type), properties: { sessionID: id, info } } });
934
- for (const child of await hooks.reviewerCorrectionSessions?.(id) ?? [])
935
- await syncReviewerCorrectionPermissions(context, hooks, child);
936
- if (event.type !== "session.deleted")
937
- await syncReviewerCorrectionPermissions(context, hooks, id);
873
+ const releaseWork = retainWorkDirectory(context.location.directory);
874
+ try {
875
+ let hooks;
876
+ const liveState = new Map();
877
+ const background = new NativeBackgroundLifecycle(context, async (dispatch, text) => {
878
+ if (dispatch.owner)
879
+ hooks.backgroundOwner?.(dispatch.callID, dispatch.owner);
880
+ const output = { status: dispatch.terminal === "succeeded" ? "completed" : dispatch.terminal === "interrupted" ? "cancelled" : "error",
881
+ output: text, metadata: { sessionID: dispatch.child, status: dispatch.terminal } };
882
+ await hooks["tool.execute.after"]?.({ tool: "task", sessionID: dispatch.parent, callID: dispatch.callID,
883
+ args: legacyToolInput("subagent", dispatch.input) }, output);
884
+ if (dispatch.child)
885
+ await syncReviewerCorrectionPermissions(context, hooks, dispatch.child);
886
+ // Inline Review already has a compact provenance receipt; retain the full diagnostic packet in its ordinary tool route.
887
+ try {
888
+ const packet = JSON.parse(output.output);
889
+ if (record(packet) && record(packet.completion_receipt))
890
+ return JSON.stringify(packet.completion_receipt);
891
+ }
892
+ catch { /* Ordinary native outputs are not necessarily JSON. */ }
893
+ return output.output;
894
+ }, (callID, restore) => hooks.backgroundOwner?.(callID, restore), (parent, callID, child, promptID) => hooks.backgroundResult?.(parent, callID, child, promptID) ?? Promise.resolve(undefined));
895
+ hooks = await legacyFactory({ directory: context.location.directory, client: legacyClient(context),
896
+ nativeBackground: background, returnReportTransport: "tool-result",
897
+ reviewerCorrectionPermissions: !!context.agent && !!context.storage && !!context.session.switchAgent }, context.options ?? {});
898
+ await registerV2Hooks(context, hooks, background, liveState);
899
+ // Recreate exact durable correction profiles at plugin setup, before resumed native Steps
900
+ // resolve their agent/tool snapshots. Storage.scan is part of Plugin 2.0.18's public domain.
901
+ if (context.storage?.scan) {
902
+ let after;
903
+ do {
904
+ const page = await context.storage.scan({ prefix: correctionStoragePrefix, ...(after ? { after } : {}) });
905
+ for (const entry of page.entries)
906
+ await syncReviewerCorrectionPermissions(context, hooks, entry.key.slice(correctionStoragePrefix.length));
907
+ after = page.next;
908
+ } while (after);
938
909
  }
939
- if (event.type === "session.compaction.ended")
940
- await hooks.event?.({ event: { type: "session.compacted", properties: { sessionID: id } } });
941
- };
942
- void (async () => {
943
- for await (const event of context.event.subscribe({ signal: controller.signal })) {
910
+ const finalize = createV2ReturnReportFinalizer(context, hooks);
911
+ const controller = new AbortController();
912
+ const eventOperations = new Map();
913
+ const handleEvent = async (event) => {
944
914
  const data = record(event.data) ? event.data : {};
945
- const lane = string(data.sessionID) ?? "filesystem";
946
- const operation = (eventOperations.get(lane) ?? Promise.resolve()).then(() => handleEvent(event)).catch(error => {
947
- console.warn("[sortie-dogs-v010] V2 event handling failed", error instanceof Error ? error.message : "unknown");
948
- }).finally(() => { if (eventOperations.get(lane) === operation)
949
- eventOperations.delete(lane); });
950
- eventOperations.set(lane, operation);
951
- }
952
- })().catch(error => {
953
- if (!controller.signal.aborted)
954
- console.warn("[sortie-dogs-v010] V2 event adapter stopped", error instanceof Error ? error.message : "unknown");
955
- });
956
- return () => controller.abort();
915
+ const id = string(data.sessionID);
916
+ if (event.type === "filesystem.changed" && typeof data.file === "string") {
917
+ await hooks.event?.({ event: { type: "file.edited", properties: { file: data.file } } });
918
+ return;
919
+ }
920
+ if (id === undefined)
921
+ return;
922
+ try {
923
+ if (String(event.type).startsWith("session.execution.") || event.type === "session.inbox.enqueued" || event.type === "session.idle")
924
+ await background.event(event);
925
+ if (event.type === "session.deleted") {
926
+ await context.storage?.remove?.(childSelectionKey(id));
927
+ liveState.delete(id);
928
+ await context.storage?.remove?.(liveStateKey(id));
929
+ // This native event is definite deletion, unlike a transient session.get failure.
930
+ await disposeCorrectionPermissions(context, id);
931
+ }
932
+ if (event.type === "session.execution.succeeded")
933
+ await finalize(id);
934
+ if (event.type === "session.created" || event.type === "session.deleted" || event.type === "session.idle") {
935
+ const info = event.type === "session.created" ? await context.session.get({ sessionID: id }).catch(() => ({ id })) : { id };
936
+ await hooks.event?.({ event: { type: String(event.type), properties: { sessionID: id, info } } });
937
+ for (const child of await hooks.reviewerCorrectionSessions?.(id) ?? [])
938
+ await syncReviewerCorrectionPermissions(context, hooks, child);
939
+ if (event.type !== "session.deleted")
940
+ await syncReviewerCorrectionPermissions(context, hooks, id);
941
+ }
942
+ if (event.type === "session.compaction.ended")
943
+ await hooks.event?.({ event: { type: "session.compacted", properties: { sessionID: id } } });
944
+ }
945
+ finally {
946
+ if (event.type === "session.deleted")
947
+ forgetWorkSession(context.location.directory, id);
948
+ }
949
+ };
950
+ void (async () => {
951
+ for await (const event of context.event.subscribe({ signal: controller.signal })) {
952
+ const data = record(event.data) ? event.data : {};
953
+ const lane = string(data.sessionID) ?? "filesystem";
954
+ const operation = (eventOperations.get(lane) ?? Promise.resolve()).then(() => handleEvent(event)).catch(error => {
955
+ console.warn("[sortie-dogs-v010] V2 event handling failed", error instanceof Error ? error.message : "unknown");
956
+ }).finally(() => { if (eventOperations.get(lane) === operation)
957
+ eventOperations.delete(lane); });
958
+ eventOperations.set(lane, operation);
959
+ }
960
+ })().catch(error => {
961
+ if (!controller.signal.aborted)
962
+ console.warn("[sortie-dogs-v010] V2 event adapter stopped", error instanceof Error ? error.message : "unknown");
963
+ });
964
+ return () => { controller.abort(); releaseWork(); };
965
+ }
966
+ catch (error) {
967
+ releaseWork();
968
+ throw error;
969
+ }
957
970
  },
958
971
  };
959
972
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sortie-dogs",
3
- "version": "0.13.19",
3
+ "version": "0.13.21",
4
4
  "description": "Bounded agent harness and validated orchestration loop plugin for OpenCode",
5
5
  "keywords": [
6
6
  "opencode",