sortie-dogs 0.16.0 → 0.16.1
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
CHANGED
|
@@ -3,6 +3,6 @@
|
|
|
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.16.
|
|
6
|
+
export declare const V010_RUNTIME_ASSET_VERSION = "0.16.1-review-git-boundary-v1";
|
|
7
7
|
export declare const CODEX_SKILL_ASSET_VERSION = "0.14.6-codex-skill-v1";
|
|
8
8
|
export type RuntimeAssetVersion = typeof RUNTIME_ASSET_VERSION | typeof V010_RUNTIME_ASSET_VERSION | typeof CODEX_SKILL_ASSET_VERSION;
|
package/dist/asset-version.js
CHANGED
|
@@ -3,5 +3,5 @@
|
|
|
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.16.
|
|
6
|
+
export const V010_RUNTIME_ASSET_VERSION = "0.16.1-review-git-boundary-v1";
|
|
7
7
|
export const CODEX_SKILL_ASSET_VERSION = "0.14.6-codex-skill-v1";
|
|
@@ -683,12 +683,66 @@ export async function missionReviewSource(directory, run, evidence = [], baselin
|
|
|
683
683
|
local.push(scoped || ".");
|
|
684
684
|
}
|
|
685
685
|
const toolInput = scope.read.some(path => normalizeManifestScope(path).path.startsWith(TOOL_ENVIRONMENT));
|
|
686
|
-
|
|
686
|
+
// A Location may inherit an ancestor repository while its actual outputs live in a
|
|
687
|
+
// nested repository/worktree. Resolve that boundary before asking the outer Git:
|
|
688
|
+
// ls-files can return the whole ignored repository (or fail internally) for a narrow output.
|
|
689
|
+
const rootLocal = [], nested = new Map();
|
|
690
|
+
for (const path of local) {
|
|
691
|
+
let current = resolve(directory), repository;
|
|
692
|
+
for (const part of path.split("/")) {
|
|
693
|
+
current = resolve(current, part);
|
|
694
|
+
const info = await lstat(current).catch((error) => {
|
|
695
|
+
if (error.code !== "ENOENT" && error.code !== "ENOTDIR")
|
|
696
|
+
throw error;
|
|
697
|
+
return undefined;
|
|
698
|
+
});
|
|
699
|
+
if (!info?.isDirectory())
|
|
700
|
+
break; // Missing files and symlink targets are not traversed.
|
|
701
|
+
if (current === resolve(directory))
|
|
702
|
+
continue;
|
|
703
|
+
const metadata = await lstat(join(current, ".git")).catch((error) => {
|
|
704
|
+
if (error.code !== "ENOENT")
|
|
705
|
+
throw error;
|
|
706
|
+
return undefined;
|
|
707
|
+
});
|
|
708
|
+
if (metadata)
|
|
709
|
+
repository = current; // Includes .git files and broken worktrees: Git must diagnose them.
|
|
710
|
+
}
|
|
711
|
+
if (repository)
|
|
712
|
+
nested.set(repository, [...(nested.get(repository) ?? []), relative(repository, resolve(directory, path)).replaceAll("\\", "/") || "."]);
|
|
713
|
+
else
|
|
714
|
+
rootLocal.push(path);
|
|
715
|
+
}
|
|
716
|
+
const gitScopes = [...rootLocal, ...(toolInput ? [] : [`:(exclude)${TOOL_ENVIRONMENT}`])];
|
|
687
717
|
const git = async (args) => (await exec("git", args, { cwd: directory, maxBuffer: 8 * 1024 * 1024 })).stdout;
|
|
688
718
|
let names = "", ignored = "", repository = true, scopedDiff = "";
|
|
689
719
|
const untracked = new Set(), currentFiles = new Set();
|
|
720
|
+
const collected = new Set();
|
|
721
|
+
const collectRepository = async (absolute, path, pathspec) => {
|
|
722
|
+
const scopedGit = async (args) => (await exec("git", args, { cwd: absolute, maxBuffer: 8 * 1024 * 1024 })).stdout;
|
|
723
|
+
const tracked = await scopedGit(["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...pathspec]);
|
|
724
|
+
const prefix = (name) => join(path, name).replaceAll("\\", "/");
|
|
725
|
+
for (const name of tracked.split("\0").filter(Boolean)) {
|
|
726
|
+
collected.add(prefix(name));
|
|
727
|
+
currentFiles.add(prefix(name));
|
|
728
|
+
}
|
|
729
|
+
for (const name of (await scopedGit(["ls-files", "-z", "--others", "--exclude-standard", "--", ...pathspec])).split("\0").filter(Boolean))
|
|
730
|
+
untracked.add(prefix(name));
|
|
731
|
+
for (const name of (await scopedGit(["ls-files", "-z", "--others", "--ignored", "--exclude-standard", "--", ...pathspec])).split("\0").filter(Boolean)) {
|
|
732
|
+
collected.add(prefix(name));
|
|
733
|
+
untracked.add(prefix(name));
|
|
734
|
+
}
|
|
735
|
+
// The Mission baseline belongs to its root, not these different repositories.
|
|
736
|
+
// Without a baseline here, committed outputs still appear as current content.
|
|
737
|
+
const delta = await currentGitDiff(scopedGit, pathspec);
|
|
738
|
+
if (delta)
|
|
739
|
+
scopedDiff += `\n--- repository output: ${path} ---\n${delta}`;
|
|
740
|
+
};
|
|
741
|
+
for (const [absolute, paths] of nested) {
|
|
742
|
+
await collectRepository(absolute, relative(directory, absolute).replaceAll("\\", "/"), paths);
|
|
743
|
+
}
|
|
690
744
|
try {
|
|
691
|
-
if (
|
|
745
|
+
if (rootLocal.length) {
|
|
692
746
|
names = await git(["ls-files", "-z", "--cached", "--others", "--exclude-standard", "--", ...gitScopes]);
|
|
693
747
|
for (const path of (await git(["ls-files", "-z", "--others", "--exclude-standard", "--", ...gitScopes])).split("\0").filter(Boolean))
|
|
694
748
|
untracked.add(path);
|
|
@@ -701,7 +755,7 @@ export async function missionReviewSource(directory, run, evidence = [], baselin
|
|
|
701
755
|
repository = false;
|
|
702
756
|
// Mission storage can be outside Git while its declared outputs are repositories,
|
|
703
757
|
// worktrees or plain files. Inventory those outputs in place, not the storage root.
|
|
704
|
-
const
|
|
758
|
+
const walked = new Set();
|
|
705
759
|
const collect = async (path) => {
|
|
706
760
|
if (walked.has(path) || excluded(path))
|
|
707
761
|
return;
|
|
@@ -719,11 +773,9 @@ export async function missionReviewSource(directory, run, evidence = [], baselin
|
|
|
719
773
|
untracked.add(path);
|
|
720
774
|
return;
|
|
721
775
|
}
|
|
722
|
-
|
|
723
|
-
const pathspec = ["."]; // Shared tooling is rooted at the Mission, not each output repository.
|
|
724
|
-
let tracked;
|
|
776
|
+
// Shared tooling is rooted at the Mission, not each output repository.
|
|
725
777
|
try {
|
|
726
|
-
|
|
778
|
+
await collectRepository(absolute, path, ["."]);
|
|
727
779
|
}
|
|
728
780
|
catch (error) {
|
|
729
781
|
if (!notRepository(error))
|
|
@@ -733,27 +785,11 @@ export async function missionReviewSource(directory, run, evidence = [], baselin
|
|
|
733
785
|
}
|
|
734
786
|
return;
|
|
735
787
|
}
|
|
736
|
-
const prefix = (name) => join(path, name).replaceAll("\\", "/");
|
|
737
|
-
for (const name of tracked.split("\0").filter(Boolean)) {
|
|
738
|
-
collected.add(prefix(name));
|
|
739
|
-
currentFiles.add(prefix(name));
|
|
740
|
-
}
|
|
741
|
-
for (const name of (await scopedGit(["ls-files", "-z", "--others", "--exclude-standard", "--", ...pathspec])).split("\0").filter(Boolean))
|
|
742
|
-
untracked.add(prefix(name));
|
|
743
|
-
for (const name of (await scopedGit(["ls-files", "-z", "--others", "--ignored", "--exclude-standard", "--", ...pathspec])).split("\0").filter(Boolean)) {
|
|
744
|
-
collected.add(prefix(name));
|
|
745
|
-
untracked.add(prefix(name));
|
|
746
|
-
}
|
|
747
|
-
// The mission's baseline belongs to its root, not to these different repositories.
|
|
748
|
-
// With no baseline here, committed outputs still appear as current file content.
|
|
749
|
-
const delta = await currentGitDiff(scopedGit, pathspec);
|
|
750
|
-
if (delta)
|
|
751
|
-
scopedDiff += `\n--- repository output: ${path} ---\n${delta}`;
|
|
752
788
|
};
|
|
753
|
-
for (const path of
|
|
789
|
+
for (const path of rootLocal)
|
|
754
790
|
await collect(path);
|
|
755
|
-
names = [...collected].sort().join("\0");
|
|
756
791
|
}
|
|
792
|
+
names = [...new Set([...names.split("\0").filter(Boolean), ...collected])].sort().join("\0");
|
|
757
793
|
// Go's ignored in-project caches may be writable during validation but are not candidate
|
|
758
794
|
// output. Keep every other ignored declared output visible and fingerprinted, including
|
|
759
795
|
// directories of generated artifacts; focused references can still pin a cache file.
|
|
@@ -763,7 +799,7 @@ export async function missionReviewSource(directory, run, evidence = [], baselin
|
|
|
763
799
|
const unreadable = [];
|
|
764
800
|
const outsideLinks = [];
|
|
765
801
|
let diff = scopedDiff;
|
|
766
|
-
if (repository &&
|
|
802
|
+
if (repository && rootLocal.length && displayBaseline) {
|
|
767
803
|
// HEAD-only diffs are empty once the author commits. Display the original mission delta
|
|
768
804
|
// or the focused correction delta without changing the full fingerprint basis.
|
|
769
805
|
const changed = (await git(["diff", "--name-only", "-z", "--no-ext-diff", displayBaseline, "--", ...gitScopes]))
|
|
@@ -774,7 +810,7 @@ export async function missionReviewSource(directory, run, evidence = [], baselin
|
|
|
774
810
|
const available = Math.max(0, 24_000 - Buffer.byteLength(selected + heading) -
|
|
775
811
|
headers.reduce((size, title) => size + Buffer.byteLength(title), 0) - 500);
|
|
776
812
|
const allowance = shown.length ? Math.min(3_000, Math.floor(available / shown.length)) : 0;
|
|
777
|
-
diff
|
|
813
|
+
diff += heading;
|
|
778
814
|
for (const [index, path] of shown.entries()) {
|
|
779
815
|
const patch = await git(["diff", "--no-ext-diff", "--no-textconv", displayBaseline, "--", path]);
|
|
780
816
|
const bytes = Buffer.from(patch);
|
|
@@ -788,8 +824,8 @@ export async function missionReviewSource(directory, run, evidence = [], baselin
|
|
|
788
824
|
}
|
|
789
825
|
omitted.push(...changed.slice(shown.length));
|
|
790
826
|
}
|
|
791
|
-
else if (repository &&
|
|
792
|
-
diff
|
|
827
|
+
else if (repository && rootLocal.length) {
|
|
828
|
+
diff += await currentGitDiff(git, gitScopes);
|
|
793
829
|
}
|
|
794
830
|
const unchanged = diff.length === 0;
|
|
795
831
|
let excerpt = selected + diff;
|
|
@@ -25,6 +25,7 @@ export declare class NativeBackgroundLifecycle {
|
|
|
25
25
|
private readonly recoverResult?;
|
|
26
26
|
private readonly operations;
|
|
27
27
|
private readonly restored;
|
|
28
|
+
private readonly reconciliationErrors;
|
|
28
29
|
constructor(context: OpenCodeV2Context, settle: (dispatch: NativeBackgroundDispatch, text: string) => Promise<string | void>, owner?: ((callID: string, restore?: ObjectValue) => ObjectValue | undefined) | undefined, recoverResult?: ((parent: string, callID: string, child: string, promptID?: string) => Promise<string | undefined>) | undefined);
|
|
29
30
|
private serial;
|
|
30
31
|
private read;
|
|
@@ -33,6 +34,7 @@ export declare class NativeBackgroundLifecycle {
|
|
|
33
34
|
bind(parent: string, child: string, agent: string, prompt: string, promptID?: string): Promise<void>;
|
|
34
35
|
launched(parent: string, callID: string, child: string | undefined, running: boolean): Promise<boolean>;
|
|
35
36
|
awaiting(sessionID: string): Promise<boolean>;
|
|
37
|
+
reconciliationNotice(parent: string): string | undefined;
|
|
36
38
|
reconcile(parent: string): Promise<void>;
|
|
37
39
|
/** Add provenance beside its existing native notification, without editing history or waking the parent again. */
|
|
38
40
|
project(parent: string, messages: readonly unknown[]): Promise<unknown[]>;
|
|
@@ -9,6 +9,7 @@ export class NativeBackgroundLifecycle {
|
|
|
9
9
|
recoverResult;
|
|
10
10
|
operations = new Map();
|
|
11
11
|
restored = new Set();
|
|
12
|
+
reconciliationErrors = new Map();
|
|
12
13
|
constructor(context, settle, owner, recoverResult) {
|
|
13
14
|
this.context = context;
|
|
14
15
|
this.settle = settle;
|
|
@@ -85,8 +86,22 @@ export class NativeBackgroundLifecycle {
|
|
|
85
86
|
return object(info) && typeof info.parentID === "string" &&
|
|
86
87
|
(await this.read(info.parentID)).some(item => !item.settled && item.child === sessionID);
|
|
87
88
|
}
|
|
89
|
+
reconciliationNotice(parent) { return this.reconciliationErrors.get(parent); }
|
|
88
90
|
async reconcile(parent) {
|
|
89
|
-
|
|
91
|
+
try {
|
|
92
|
+
await this.serial(parent, async () => this.reconcileInside(parent, await this.read(parent)));
|
|
93
|
+
this.reconciliationErrors.delete(parent);
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
// This is recovery of existing native work, not admission or acceptance. A failed
|
|
97
|
+
// inspection must retain its terminal/reservation for idempotent retry without
|
|
98
|
+
// making the next user prompt, model request or compaction fail before it starts.
|
|
99
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
100
|
+
console.warn("[sortie-dogs-v010] Background reconciliation unavailable; saved work retained", reason);
|
|
101
|
+
this.reconciliationErrors.set(parent, `SORTIE_BACKGROUND_RECONCILIATION_UNAVAILABLE\n${JSON.stringify(reason.slice(0, 2_000))}\n` +
|
|
102
|
+
"Native work/results and cumulative spend are retained; formal validation and acceptance are not proven by this recovery failure. " +
|
|
103
|
+
"Diagnose the failed inspection and reconcile this same invocation after repair. Do not relaunch or repeat an already completed external operation just to repair accounting.");
|
|
104
|
+
}
|
|
90
105
|
}
|
|
91
106
|
/** Add provenance beside its existing native notification, without editing history or waking the parent again. */
|
|
92
107
|
async project(parent, messages) {
|
package/dist/plugin/v2.js
CHANGED
|
@@ -847,6 +847,9 @@ async function registerV2Hooks(context, hooks, background, liveState) {
|
|
|
847
847
|
event.messages = await background.project(String(event.sessionID ?? ""), event.messages);
|
|
848
848
|
const output = { system: [] };
|
|
849
849
|
await hooks["experimental.chat.system.transform"]({ sessionID: String(event.sessionID ?? "") }, output);
|
|
850
|
+
const reconciliation = background.reconciliationNotice(String(event.sessionID ?? ""));
|
|
851
|
+
if (reconciliation)
|
|
852
|
+
(output.system ??= []).push(reconciliation);
|
|
850
853
|
await reconcileCorrectionPermissions(String(event.sessionID));
|
|
851
854
|
if (record(event.tools)) {
|
|
852
855
|
const correcting = correctionAgent(event.agent) && !!await hooks.reviewerCorrectionScope?.(String(event.sessionID));
|
|
@@ -913,6 +916,9 @@ async function registerV2Hooks(context, hooks, background, liveState) {
|
|
|
913
916
|
}
|
|
914
917
|
const output = { context: [] };
|
|
915
918
|
await hooks["experimental.session.compacting"]({ sessionID: String(event.sessionID ?? "") }, output);
|
|
919
|
+
const reconciliation = background.reconciliationNotice(String(event.sessionID ?? ""));
|
|
920
|
+
if (reconciliation)
|
|
921
|
+
(output.context ??= []).push(reconciliation);
|
|
916
922
|
if (Array.isArray(event.system))
|
|
917
923
|
event.system.push(...output.context.map(text => ({ type: "text", text })));
|
|
918
924
|
});
|