@wrongstack/core 0.300.0 → 0.302.0
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/chronicle/index.d.ts +2 -0
- package/dist/chronicle/index.js +297 -10
- package/dist/chronicle/project-server.js +253 -15
- package/dist/chronicle/sqlite-compaction.d.ts +20 -0
- package/dist/chronicle/sqlite-journal.d.ts +57 -0
- package/dist/coordination/agents/capability-manifest.d.ts +7 -0
- package/dist/coordination/agents/index.d.ts +3 -2
- package/dist/coordination/agents/index.js +818 -570
- package/dist/coordination/agents/project-agent-identity.d.ts +6 -6
- package/dist/coordination/agents/role-skills.d.ts +1 -1
- package/dist/coordination/agents/types.d.ts +6 -0
- package/dist/coordination/director.d.ts +2 -2
- package/dist/coordination/index.js +6787 -6447
- package/dist/coordination/mail-tools.d.ts +3 -3
- package/dist/coordination/mailbox-project-server.js +3 -4
- package/dist/coordination/mailbox-types.d.ts +6 -0
- package/dist/core/agent-response.d.ts +5 -1
- package/dist/core/agent-tools.d.ts +3 -0
- package/dist/core/context.d.ts +20 -0
- package/dist/core/index.d.ts +8 -7
- package/dist/core/index.js +893 -284
- package/dist/core/instruction-template.d.ts +6 -3
- package/dist/core/next-steps-slot.d.ts +88 -0
- package/dist/core/system-prompt-builder.d.ts +5 -3
- package/dist/core/system-prompt-memory-skills.d.ts +1 -0
- package/dist/core/system-prompt-skill-bodies.d.ts +3 -3
- package/dist/defaults/index.js +9797 -8898
- package/dist/design/index.js +11 -3
- package/dist/execution/auto-compaction-middleware.d.ts +17 -0
- package/dist/execution/autonomy-brain.d.ts +8 -2
- package/dist/execution/brain-runtime.d.ts +3 -1
- package/dist/execution/compaction-core.d.ts +41 -2
- package/dist/execution/council-brain.d.ts +37 -2
- package/dist/execution/council-orchestrator.d.ts +2 -2
- package/dist/execution/index.d.ts +11 -10
- package/dist/execution/index.js +5179 -4078
- package/dist/execution/tool-executor.d.ts +4 -1
- package/dist/execution/topic-shift-advisor.d.ts +53 -0
- package/dist/extension/index.js +8 -2
- package/dist/extension/registry.d.ts +8 -1
- package/dist/goal/index.js +11 -3
- package/dist/hooks/index.js +42 -9
- package/dist/hooks/runner.d.ts +12 -1
- package/dist/hq/index.js +41 -17
- package/dist/hq/publisher.d.ts +21 -3
- package/dist/index.d.ts +21 -20
- package/dist/index.js +10901 -8067
- package/dist/infrastructure/index.js +108 -21
- package/dist/kernel/events/session-events.d.ts +13 -0
- package/dist/kernel/events/tool-events.d.ts +3 -3
- package/dist/models/index.d.ts +1 -0
- package/dist/models/index.js +17 -0
- package/dist/models/provider-credentials.d.ts +54 -0
- package/dist/plugin/index.d.ts +1 -0
- package/dist/plugin/index.js +2024 -502
- package/dist/plugins/auto-review-plugin.d.ts +3 -0
- package/dist/plugins/cloud-config-sync-plugin.d.ts +22 -0
- package/dist/plugins/review-claim-registry.d.ts +23 -8
- package/dist/plugins/review-types.d.ts +2 -0
- package/dist/registry/index.js +109 -74
- package/dist/registry/tool-registry.d.ts +15 -0
- package/dist/security/capabilities.d.ts +2 -0
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +9 -2
- package/dist/security/readonly-permission-policy.d.ts +20 -0
- package/dist/session-registry-atomic-file.d.ts +32 -5
- package/dist/session-registry-types.d.ts +17 -0
- package/dist/session-registry.d.ts +1 -1
- package/dist/skills/frontmatter.d.ts +6 -0
- package/dist/skills/index.js +22 -5
- package/dist/storage/cloud-config-sync/sanitize.d.ts +54 -0
- package/dist/storage/cloud-config-sync.d.ts +87 -0
- package/dist/storage/index.d.ts +2 -1
- package/dist/storage/index.js +854 -66
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +511 -234
- package/dist/tools/one-shot-llm-tool.d.ts +1 -0
- package/dist/tools/plugin-manager.d.ts +27 -0
- package/dist/types/config/mcp-features.d.ts +18 -11
- package/dist/types/config/root.d.ts +8 -1
- package/dist/types/config/runtime.d.ts +20 -6
- package/dist/types/config/skills-fleet-brain.d.ts +12 -4
- package/dist/types/config/tools.d.ts +16 -0
- package/dist/types/context-window.d.ts +1 -0
- package/dist/types/hooks.d.ts +14 -0
- package/dist/types/index.d.ts +2 -2
- package/dist/types/index.js +1 -0
- package/dist/types/multi-agent.d.ts +2 -0
- package/dist/types/one-shot-llm.d.ts +16 -0
- package/dist/types/provider.d.ts +16 -0
- package/dist/types/runtime-capability-manifest.d.ts +168 -0
- package/dist/types/skill.d.ts +5 -0
- package/dist/types/system-prompt.d.ts +3 -1
- package/dist/types/tool-executor.d.ts +8 -1
- package/dist/utils/context-breakdown.d.ts +8 -2
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js +141 -31
- package/dist/utils/regex-guard.d.ts +16 -2
- package/dist/utils/sage-output-block.d.ts +7 -10
- package/dist/utils/wstack-paths.d.ts +11 -3
- package/instructions/agents/browser.md +1 -4
- package/instructions/agents/e2e.md +17 -15
- package/instructions/agents/ios.md +3 -3
- package/instructions/agents/search.md +1 -1
- package/instructions/autonomy/goal-preamble.md +4 -4
- package/instructions/coordination/director-preamble.md +2 -3
- package/instructions/coordination/subagent-baseline.md +3 -1
- package/instructions/leader-after-task.md +12 -0
- package/instructions/llm/chimera-review.md +1 -1
- package/instructions/modes/audit-lite.md +1 -1
- package/instructions/sections/tool/common-patterns.md +18 -2
- package/instructions/sections/tool/mailbox-compact.md +1 -1
- package/instructions/sections/tool/mailbox-full.md +1 -1
- package/instructions/system-lite.md +10 -0
- package/instructions/system-pro.md +23 -14
- package/instructions/system.md +22 -13
- package/package.json +3 -3
- package/skills/api-design/SKILL.md +3 -0
- package/skills/audit-log/SKILL.md +2 -0
- package/skills/auto-review/SKILL.md +6 -1
- package/skills/bug-hunter/SKILL.md +4 -1
- package/skills/chimera/SKILL.md +3 -0
- package/skills/data-governance/SKILL.md +3 -0
- package/skills/design-system/SKILL.md +5 -2
- package/skills/docker-deploy/SKILL.md +2 -0
- package/skills/git-flow/SKILL.md +2 -0
- package/skills/mailbox-bridge/SKILL.md +3 -0
- package/skills/mnemosyne/SKILL.md +2 -0
- package/skills/multi-agent/SKILL.md +4 -1
- package/skills/node-modern/SKILL.md +4 -1
- package/skills/observability/SKILL.md +3 -0
- package/skills/output-standards/SKILL.md +2 -0
- package/skills/plugin-author/SKILL.md +4 -1
- package/skills/prompt-engineering/SKILL.md +3 -1
- package/skills/react-modern/SKILL.md +6 -3
- package/skills/refactor-planner/SKILL.md +2 -0
- package/skills/research-web/SKILL.md +3 -0
- package/skills/sdd/SKILL.md +3 -1
- package/skills/security-scanner/SKILL.md +3 -0
- package/skills/skill-creator/SKILL.md +2 -0
- package/skills/tech-stack/SKILL.md +3 -0
- package/skills/testing/SKILL.md +4 -1
- package/skills/typescript-strict/SKILL.md +4 -1
- package/skills/wrongstack-kanban/SKILL.md +6 -3
- package/skills/wrongstack-mailbox/SKILL.md +4 -1
- package/skills/wrongstack-mailbox-mcp/SKILL.md +10 -8
package/dist/storage/index.js
CHANGED
|
@@ -854,6 +854,9 @@ function canonicalProjectRoot(absRoot) {
|
|
|
854
854
|
const commonDirFile = path2.join(gitDir, "commondir");
|
|
855
855
|
if (!fs2.statSync(commonDirFile).isFile()) return checkoutRoot;
|
|
856
856
|
const commonDir = path2.resolve(gitDir, fs2.readFileSync(commonDirFile, "utf8").trim());
|
|
857
|
+
const worktreesDir = path2.dirname(gitDir);
|
|
858
|
+
if (path2.basename(worktreesDir).toLowerCase() !== "worktrees") return checkoutRoot;
|
|
859
|
+
if (path2.resolve(worktreesDir, "..") !== commonDir) return checkoutRoot;
|
|
857
860
|
if (path2.basename(commonDir).toLowerCase() !== ".git") return checkoutRoot;
|
|
858
861
|
return path2.dirname(commonDir);
|
|
859
862
|
} catch {
|
|
@@ -876,7 +879,7 @@ function safeProfileName(name) {
|
|
|
876
879
|
const safe = (name ?? "").replace(/[/\\:]/g, "_").replace(/\.\./g, "_").trim();
|
|
877
880
|
return safe || "default";
|
|
878
881
|
}
|
|
879
|
-
function
|
|
882
|
+
function bootstrapProfileName(globalRoot) {
|
|
880
883
|
try {
|
|
881
884
|
const parsed = JSON.parse(fs2.readFileSync(path2.join(globalRoot, "config.json"), "utf8"));
|
|
882
885
|
return safeProfileName(
|
|
@@ -894,7 +897,7 @@ function wstackGlobalRoot() {
|
|
|
894
897
|
function resolveWstackPaths(opts) {
|
|
895
898
|
const globalRoot = opts.globalRoot ?? (opts.userHome ? path2.join(opts.userHome, ".wrongstack") : wstackGlobalRoot());
|
|
896
899
|
const homeDir = opts.userHome ?? os.homedir();
|
|
897
|
-
const profileName = safeProfileName(opts.profileName ??
|
|
900
|
+
const profileName = safeProfileName(opts.profileName ?? bootstrapProfileName(globalRoot));
|
|
898
901
|
const profileDir = path2.join(globalRoot, "profiles", profileName);
|
|
899
902
|
const hash = projectHash(opts.projectRoot);
|
|
900
903
|
const slug = projectSlug(opts.projectRoot);
|
|
@@ -964,7 +967,12 @@ function resolveWstackPaths(opts) {
|
|
|
964
967
|
projectRequirementIntakes: path2.join(projectDir, "requirement-intakes"),
|
|
965
968
|
syncConfig: path2.join(profileDir, "sync.json"),
|
|
966
969
|
configHistoryDir: path2.join(globalRoot, "config-history"),
|
|
967
|
-
projectStatus: (
|
|
970
|
+
projectStatus: (statusProjectSlug) => {
|
|
971
|
+
if (!/^[a-z0-9](?:[a-z0-9-]{0,39})-[a-f0-9]{6}$/.test(statusProjectSlug)) {
|
|
972
|
+
throw new Error(`Invalid project slug: ${statusProjectSlug}`);
|
|
973
|
+
}
|
|
974
|
+
return path2.join(globalRoot, "projects", statusProjectSlug, "status.json");
|
|
975
|
+
}
|
|
968
976
|
};
|
|
969
977
|
}
|
|
970
978
|
|
|
@@ -1056,13 +1064,93 @@ function ulid(seedTime = Date.now()) {
|
|
|
1056
1064
|
}
|
|
1057
1065
|
|
|
1058
1066
|
// src/utils/regex-guard.ts
|
|
1059
|
-
var MAX_PATTERN_LEN =
|
|
1067
|
+
var MAX_PATTERN_LEN = 256;
|
|
1060
1068
|
var DANGEROUS_PATTERNS = [
|
|
1069
|
+
// (a+)+, (.*)+, etc — nested quantifier on a group with internal quantifier
|
|
1061
1070
|
/(\([^)]*[+*][^)]*\))[+*]/,
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1071
|
+
/(\(\?:[^)]*[+*][^)]*\))[+*]/,
|
|
1072
|
+
// Adjacent quantifiers: a++ a*+
|
|
1073
|
+
/[+*]{2,}/,
|
|
1074
|
+
// Quantifier on alternation with length 2+
|
|
1075
|
+
/\([^|)]+\|[^)]+\)[+*][+*]/,
|
|
1076
|
+
// Greedy quantifier inside lookahead/lookbehind — (?!.*a+)
|
|
1077
|
+
/[([][^)\]]*[+*][^)\]]*[)\]][^)]*\?\??/
|
|
1065
1078
|
];
|
|
1079
|
+
function hasAmbiguousQuantifiedAlternation(pattern) {
|
|
1080
|
+
for (let i = 0; i < pattern.length; i++) {
|
|
1081
|
+
if (pattern[i] !== "(") continue;
|
|
1082
|
+
if (i > 0 && pattern[i - 1] === "\\") continue;
|
|
1083
|
+
let depth = 0;
|
|
1084
|
+
let inClass = false;
|
|
1085
|
+
let j = i;
|
|
1086
|
+
for (; j < pattern.length; j++) {
|
|
1087
|
+
const ch = pattern[j];
|
|
1088
|
+
if (ch === "\\") {
|
|
1089
|
+
j++;
|
|
1090
|
+
continue;
|
|
1091
|
+
}
|
|
1092
|
+
if (inClass) {
|
|
1093
|
+
if (ch === "]") inClass = false;
|
|
1094
|
+
continue;
|
|
1095
|
+
}
|
|
1096
|
+
if (ch === "[") {
|
|
1097
|
+
inClass = true;
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
if (ch === "(") depth++;
|
|
1101
|
+
else if (ch === ")") {
|
|
1102
|
+
depth--;
|
|
1103
|
+
if (depth === 0) break;
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
if (j >= pattern.length) return false;
|
|
1107
|
+
const next = pattern[j + 1];
|
|
1108
|
+
if (next !== "+" && next !== "*" && next !== "{") continue;
|
|
1109
|
+
let inner = pattern.slice(i + 1, j);
|
|
1110
|
+
inner = inner.replace(/^\?(?::|<?[=!])/u, "");
|
|
1111
|
+
const branches = [];
|
|
1112
|
+
let current = "";
|
|
1113
|
+
let d = 0;
|
|
1114
|
+
let cls = false;
|
|
1115
|
+
for (let k = 0; k < inner.length; k++) {
|
|
1116
|
+
const ch = inner[k];
|
|
1117
|
+
if (ch === "\\") {
|
|
1118
|
+
current += ch + (inner[k + 1] ?? "");
|
|
1119
|
+
k++;
|
|
1120
|
+
continue;
|
|
1121
|
+
}
|
|
1122
|
+
if (cls) {
|
|
1123
|
+
if (ch === "]") cls = false;
|
|
1124
|
+
current += ch;
|
|
1125
|
+
continue;
|
|
1126
|
+
}
|
|
1127
|
+
if (ch === "[") {
|
|
1128
|
+
cls = true;
|
|
1129
|
+
current += ch;
|
|
1130
|
+
continue;
|
|
1131
|
+
}
|
|
1132
|
+
if (ch === "(") d++;
|
|
1133
|
+
if (ch === ")") d--;
|
|
1134
|
+
if (ch === "|" && d === 0) {
|
|
1135
|
+
branches.push(current);
|
|
1136
|
+
current = "";
|
|
1137
|
+
continue;
|
|
1138
|
+
}
|
|
1139
|
+
current += ch;
|
|
1140
|
+
}
|
|
1141
|
+
branches.push(current);
|
|
1142
|
+
if (branches.length < 2) continue;
|
|
1143
|
+
for (let a = 0; a < branches.length; a++) {
|
|
1144
|
+
for (let b = a + 1; b < branches.length; b++) {
|
|
1145
|
+
const x = branches[a];
|
|
1146
|
+
const y = branches[b];
|
|
1147
|
+
if (x === "" || y === "") return true;
|
|
1148
|
+
if (x === y || x.startsWith(y) || y.startsWith(x)) return true;
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
return false;
|
|
1153
|
+
}
|
|
1066
1154
|
function compileUserRegex(pattern, flags) {
|
|
1067
1155
|
if (typeof pattern !== "string") {
|
|
1068
1156
|
return { ok: false, reason: "pattern must be a string" };
|
|
@@ -1081,6 +1169,12 @@ function compileUserRegex(pattern, flags) {
|
|
|
1081
1169
|
};
|
|
1082
1170
|
}
|
|
1083
1171
|
}
|
|
1172
|
+
if (hasAmbiguousQuantifiedAlternation(pattern)) {
|
|
1173
|
+
return {
|
|
1174
|
+
ok: false,
|
|
1175
|
+
reason: "pattern quantifies an alternation with overlapping branches \u2014 rewrite so no two branches can match the same text"
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1084
1178
|
try {
|
|
1085
1179
|
return { ok: true, regex: new RegExp(pattern, flags) };
|
|
1086
1180
|
} catch (err) {
|
|
@@ -1090,6 +1184,7 @@ function compileUserRegex(pattern, flags) {
|
|
|
1090
1184
|
};
|
|
1091
1185
|
}
|
|
1092
1186
|
}
|
|
1187
|
+
var MAX_SUBJECT_LEN = 64 * 1024;
|
|
1093
1188
|
|
|
1094
1189
|
// src/utils/safe-json.ts
|
|
1095
1190
|
function safeParse(input, maxBytes = 5e6) {
|
|
@@ -2731,8 +2826,8 @@ function scrubPersistedSessionEvent(event, scrubber) {
|
|
|
2731
2826
|
function scrubPersistedSessionData(data, scrubber) {
|
|
2732
2827
|
return scrubber.scrubObject(data);
|
|
2733
2828
|
}
|
|
2734
|
-
function scrubPersistedSessionSummary(
|
|
2735
|
-
const scrubbed = scrubber.scrubObject(
|
|
2829
|
+
function scrubPersistedSessionSummary(summary2, scrubber) {
|
|
2830
|
+
const scrubbed = scrubber.scrubObject(summary2);
|
|
2736
2831
|
const { name: _name, lastUserMessage: _lastUserMessage, ...rest } = scrubbed;
|
|
2737
2832
|
const title = sessionContentText(scrubbed.title) || "(empty session)";
|
|
2738
2833
|
const name = scrubbed.name === void 0 ? "" : sessionContentText(scrubbed.name);
|
|
@@ -3639,15 +3734,15 @@ function compareSessionSummaries(a, b) {
|
|
|
3639
3734
|
if (a.startedAt > b.startedAt) return -1;
|
|
3640
3735
|
return a.id.localeCompare(b.id);
|
|
3641
3736
|
}
|
|
3642
|
-
function matchesSessionFilter(
|
|
3643
|
-
if (criteria.since &&
|
|
3644
|
-
if (criteria.until &&
|
|
3645
|
-
if (criteria.provider &&
|
|
3646
|
-
if (criteria.model &&
|
|
3647
|
-
if (criteria.minTokens !== void 0 &&
|
|
3737
|
+
function matchesSessionFilter(summary2, criteria) {
|
|
3738
|
+
if (criteria.since && summary2.startedAt < criteria.since) return false;
|
|
3739
|
+
if (criteria.until && summary2.startedAt > criteria.until) return false;
|
|
3740
|
+
if (criteria.provider && summary2.provider !== criteria.provider) return false;
|
|
3741
|
+
if (criteria.model && summary2.model !== criteria.model) return false;
|
|
3742
|
+
if (criteria.minTokens !== void 0 && summary2.tokenTotal < criteria.minTokens) return false;
|
|
3648
3743
|
if (criteria.titleContains) {
|
|
3649
3744
|
const needle = criteria.titleContains.toLocaleLowerCase();
|
|
3650
|
-
if (!
|
|
3745
|
+
if (!summary2.title.toLocaleLowerCase().includes(needle)) return false;
|
|
3651
3746
|
}
|
|
3652
3747
|
return true;
|
|
3653
3748
|
}
|
|
@@ -3670,15 +3765,15 @@ async function readOrBuildShardManifestEntry(opts) {
|
|
|
3670
3765
|
async (ref) => {
|
|
3671
3766
|
const manifest = await opts.readSummaryManifest(ref.id);
|
|
3672
3767
|
if (manifest) return { summary: manifest, needsBackfill: false };
|
|
3673
|
-
const
|
|
3674
|
-
if (!
|
|
3675
|
-
const hydrated = await opts.summaryFor(
|
|
3768
|
+
const summary2 = await opts.summaryHeaderFor(ref);
|
|
3769
|
+
if (!summary2) return null;
|
|
3770
|
+
const hydrated = await opts.summaryFor(summary2.id).catch(() => summary2);
|
|
3676
3771
|
return { summary: hydrated, needsBackfill: false };
|
|
3677
3772
|
}
|
|
3678
3773
|
);
|
|
3679
3774
|
const summaries = candidates.filter((candidate) => candidate !== null).map((candidate) => candidate.summary);
|
|
3680
3775
|
summaries.sort(compareSessionSummaries);
|
|
3681
|
-
const entry = { summaries, ids: summaries.map((
|
|
3776
|
+
const entry = { summaries, ids: summaries.map((summary2) => summary2.id) };
|
|
3682
3777
|
await atomicWrite(opts.manifestPath, JSON.stringify(entry), { mode: 384 }).catch(
|
|
3683
3778
|
() => void 0
|
|
3684
3779
|
);
|
|
@@ -3925,7 +4020,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
3925
4020
|
}
|
|
3926
4021
|
}
|
|
3927
4022
|
scrubSummaries(summaries) {
|
|
3928
|
-
return summaries.map((
|
|
4023
|
+
return summaries.map((summary2) => scrubPersistedSessionSummary(summary2, this.secretScrubber));
|
|
3929
4024
|
}
|
|
3930
4025
|
/**
|
|
3931
4026
|
* Clear the load() cache. Useful for testing or when the caller knows
|
|
@@ -4304,12 +4399,12 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
4304
4399
|
indexAppendCount = 0;
|
|
4305
4400
|
static COMPACT_EVERY = 30;
|
|
4306
4401
|
/** Append a session summary to the index, propagating persistence failures. */
|
|
4307
|
-
async appendToIndexStrict(
|
|
4402
|
+
async appendToIndexStrict(summary2) {
|
|
4308
4403
|
await ensureDir(this.dir);
|
|
4309
4404
|
let shouldCompact = false;
|
|
4310
4405
|
await withFileLock(this.indexFile, async () => {
|
|
4311
|
-
await this.invalidateShardManifestBySessionId(
|
|
4312
|
-
const line = JSON.stringify(
|
|
4406
|
+
await this.invalidateShardManifestBySessionId(summary2.id);
|
|
4407
|
+
const line = JSON.stringify(summary2) + "\n";
|
|
4313
4408
|
await fsp12.appendFile(this.indexFile, line, { encoding: "utf8", mode: SECRET_FILE_MODE });
|
|
4314
4409
|
this._indexCache = null;
|
|
4315
4410
|
this.indexAppendCount++;
|
|
@@ -4323,8 +4418,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
4323
4418
|
}
|
|
4324
4419
|
}
|
|
4325
4420
|
/** Best-effort index append used by writer close. */
|
|
4326
|
-
async appendToIndex(
|
|
4327
|
-
await this.appendToIndexStrict(
|
|
4421
|
+
async appendToIndex(summary2) {
|
|
4422
|
+
await this.appendToIndexStrict(summary2).catch(() => {
|
|
4328
4423
|
});
|
|
4329
4424
|
}
|
|
4330
4425
|
/** Append a tombstone entry for a deleted session. */
|
|
@@ -4457,8 +4552,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
4457
4552
|
);
|
|
4458
4553
|
const out = [];
|
|
4459
4554
|
for (const entry of shardEntries) {
|
|
4460
|
-
for (const
|
|
4461
|
-
out.push({ summary, needsBackfill: false });
|
|
4555
|
+
for (const summary2 of entry.summaries) {
|
|
4556
|
+
out.push({ summary: summary2, needsBackfill: false });
|
|
4462
4557
|
}
|
|
4463
4558
|
}
|
|
4464
4559
|
out.sort((a, b) => compareSessionSummaries(a.summary, b.summary));
|
|
@@ -4529,8 +4624,8 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
4529
4624
|
try {
|
|
4530
4625
|
const full = this.sessionPath(id, ".jsonl");
|
|
4531
4626
|
const stat15 = await fsp12.stat(full);
|
|
4532
|
-
const
|
|
4533
|
-
await atomicWrite(manifest, JSON.stringify(
|
|
4627
|
+
const summary2 = await this.summarize(id, stat15.mtime.toISOString());
|
|
4628
|
+
await atomicWrite(manifest, JSON.stringify(summary2), { mode: 384 }).catch((err) => {
|
|
4534
4629
|
const msg = toErrorMessage(err);
|
|
4535
4630
|
emitSessionStoreError(this.events, id, manifest, "summary_fallback", msg, true);
|
|
4536
4631
|
this.logWarn("Session manifest write failed", {
|
|
@@ -4550,7 +4645,7 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
4550
4645
|
Date.now() - t0,
|
|
4551
4646
|
errorMsg
|
|
4552
4647
|
);
|
|
4553
|
-
return
|
|
4648
|
+
return summary2;
|
|
4554
4649
|
} catch (err) {
|
|
4555
4650
|
outcome = "failure";
|
|
4556
4651
|
errorMsg = toErrorMessage(err);
|
|
@@ -4623,14 +4718,14 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
4623
4718
|
let updated;
|
|
4624
4719
|
try {
|
|
4625
4720
|
updated = await withFileLock(manifest, async () => {
|
|
4626
|
-
const
|
|
4627
|
-
const { name: _drop, ...rest } =
|
|
4721
|
+
const summary2 = await this.readSummaryManifest(id) ?? await this.summaryFor(id);
|
|
4722
|
+
const { name: _drop, ...rest } = summary2;
|
|
4628
4723
|
const next = trimmed ? { ...rest, name: trimmed } : rest;
|
|
4629
4724
|
await atomicWrite(manifest, JSON.stringify(next), { mode: 384 });
|
|
4630
4725
|
try {
|
|
4631
4726
|
await this.appendToIndexStrict(next);
|
|
4632
4727
|
} catch (err) {
|
|
4633
|
-
await atomicWrite(manifest, JSON.stringify(
|
|
4728
|
+
await atomicWrite(manifest, JSON.stringify(summary2), { mode: 384 });
|
|
4634
4729
|
throw err;
|
|
4635
4730
|
}
|
|
4636
4731
|
return next;
|
|
@@ -6607,7 +6702,7 @@ var CONFIG_BEHAVIOR_DEFAULTS = {
|
|
|
6607
6702
|
enabled: false
|
|
6608
6703
|
}
|
|
6609
6704
|
},
|
|
6610
|
-
skills: { readClaudeSkills: true },
|
|
6705
|
+
skills: { readClaudeSkills: true, mode: "progressive" },
|
|
6611
6706
|
mcpServers: {},
|
|
6612
6707
|
fallbackAuto: true,
|
|
6613
6708
|
maxConcurrent: 4,
|
|
@@ -6707,19 +6802,19 @@ function repairConfigDefaults(input) {
|
|
|
6707
6802
|
});
|
|
6708
6803
|
}
|
|
6709
6804
|
for (const [key, defaultValue] of Object.entries(defaults)) {
|
|
6710
|
-
const
|
|
6805
|
+
const path31 = prefix ? `${prefix}.${key}` : key;
|
|
6711
6806
|
if (!Object.hasOwn(target, key)) {
|
|
6712
6807
|
target[key] = cloneJsonValue(defaultValue);
|
|
6713
|
-
changes.push({ path:
|
|
6808
|
+
changes.push({ path: path31, action: "added" });
|
|
6714
6809
|
continue;
|
|
6715
6810
|
}
|
|
6716
6811
|
const current = target[key];
|
|
6717
6812
|
if (isPlainRecord(defaultValue)) {
|
|
6718
6813
|
if (isPlainRecord(current)) {
|
|
6719
|
-
repair(current, defaultValue,
|
|
6814
|
+
repair(current, defaultValue, path31, depth + 1);
|
|
6720
6815
|
} else {
|
|
6721
6816
|
target[key] = cloneJsonValue(defaultValue);
|
|
6722
|
-
changes.push({ path:
|
|
6817
|
+
changes.push({ path: path31, action: "replaced" });
|
|
6723
6818
|
}
|
|
6724
6819
|
continue;
|
|
6725
6820
|
}
|
|
@@ -6727,7 +6822,7 @@ function repairConfigDefaults(input) {
|
|
|
6727
6822
|
const compatible = defaultIsArray ? Array.isArray(current) : !Array.isArray(current) && typeof current === typeof defaultValue;
|
|
6728
6823
|
if (!compatible) {
|
|
6729
6824
|
target[key] = cloneJsonValue(defaultValue);
|
|
6730
|
-
changes.push({ path:
|
|
6825
|
+
changes.push({ path: path31, action: "replaced" });
|
|
6731
6826
|
}
|
|
6732
6827
|
}
|
|
6733
6828
|
};
|
|
@@ -6849,6 +6944,10 @@ var KNOWN_DENIED_IN_PROJECT = [
|
|
|
6849
6944
|
reason: "Controls which plugin boot states the LLM may mutate; user-owned trust policy."
|
|
6850
6945
|
},
|
|
6851
6946
|
{ key: "sync", reason: "Carries githubToken credential and target repo." },
|
|
6947
|
+
{
|
|
6948
|
+
key: "cloudSync",
|
|
6949
|
+
reason: "Carries the my.wrongstack.com machine token credential and endpoint URL."
|
|
6950
|
+
},
|
|
6852
6951
|
{ key: "yolo", reason: "Disables all permission confirmation prompts." },
|
|
6853
6952
|
{ key: "extensions", reason: "Per-plugin config can carry command/credential fields." },
|
|
6854
6953
|
{ key: "hq", reason: "Carries HQ client token credential and endpoint URL." },
|
|
@@ -6911,6 +7010,7 @@ var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
6911
7010
|
"modelRuntime",
|
|
6912
7011
|
"hq",
|
|
6913
7012
|
"sync",
|
|
7013
|
+
"cloudSync",
|
|
6914
7014
|
"extensions",
|
|
6915
7015
|
"acp",
|
|
6916
7016
|
"fleet",
|
|
@@ -6967,8 +7067,8 @@ var IN_PROJECT_DENIED_PATHS = [
|
|
|
6967
7067
|
reason: "The other half of the filesystem confinement switch."
|
|
6968
7068
|
}
|
|
6969
7069
|
];
|
|
6970
|
-
function deleteNestedPath(target,
|
|
6971
|
-
const segments =
|
|
7070
|
+
function deleteNestedPath(target, path31) {
|
|
7071
|
+
const segments = path31.split(".");
|
|
6972
7072
|
const last = segments[segments.length - 1];
|
|
6973
7073
|
if (last === void 0) return false;
|
|
6974
7074
|
let cursor = target;
|
|
@@ -7025,16 +7125,16 @@ function assertInProjectAllowListComplete() {
|
|
|
7025
7125
|
);
|
|
7026
7126
|
}
|
|
7027
7127
|
const orphanedPaths = IN_PROJECT_DENIED_PATHS.filter(
|
|
7028
|
-
({ path:
|
|
7029
|
-
).map(({ path:
|
|
7128
|
+
({ path: path31 }) => !IN_PROJECT_ALLOWED_KEYS.has(path31.split(".")[0] ?? "")
|
|
7129
|
+
).map(({ path: path31 }) => path31);
|
|
7030
7130
|
if (orphanedPaths.length > 0) {
|
|
7031
7131
|
problems.push(
|
|
7032
7132
|
`IN_PROJECT_DENIED_PATHS entr(ies) whose top-level parent is not allowed: ` + orphanedPaths.join(", ") + ". The parent is already stripped wholesale, so the nested denial is dead \u2014 remove it."
|
|
7033
7133
|
);
|
|
7034
7134
|
}
|
|
7035
7135
|
const malformedPaths = IN_PROJECT_DENIED_PATHS.filter(
|
|
7036
|
-
({ path:
|
|
7037
|
-
).map(({ path:
|
|
7136
|
+
({ path: path31 }) => path31.split(".").length < 2 || path31.split(".").some((s) => s.length === 0)
|
|
7137
|
+
).map(({ path: path31 }) => path31);
|
|
7038
7138
|
if (malformedPaths.length > 0) {
|
|
7039
7139
|
problems.push(
|
|
7040
7140
|
`IN_PROJECT_DENIED_PATHS entr(ies) are not dotted nested paths: ` + malformedPaths.join(", ") + ". Top-level keys belong in KNOWN_DENIED_IN_PROJECT instead."
|
|
@@ -7062,8 +7162,8 @@ function stripUnsafeInProjectFields(inProject, sourcePath, warn = (msg) => conso
|
|
|
7062
7162
|
}
|
|
7063
7163
|
stripped.push(k);
|
|
7064
7164
|
}
|
|
7065
|
-
for (const { path:
|
|
7066
|
-
if (deleteNestedPath(out,
|
|
7165
|
+
for (const { path: path31 } of IN_PROJECT_DENIED_PATHS) {
|
|
7166
|
+
if (deleteNestedPath(out, path31)) stripped.push(path31);
|
|
7067
7167
|
}
|
|
7068
7168
|
if (stripped.length > 0) {
|
|
7069
7169
|
warn(
|
|
@@ -9618,30 +9718,86 @@ import * as path24 from "node:path";
|
|
|
9618
9718
|
// src/session-registry-atomic-file.ts
|
|
9619
9719
|
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
9620
9720
|
import * as fs11 from "node:fs/promises";
|
|
9721
|
+
import { hostname as hostname2 } from "node:os";
|
|
9621
9722
|
import * as path23 from "node:path";
|
|
9622
9723
|
var STALE_LOCK_MS = 1e4;
|
|
9724
|
+
var SAME_HOST_STALE_MS = 2 * STALE_LOCK_MS;
|
|
9623
9725
|
var STALE_TMP_MS = 6e4;
|
|
9624
9726
|
var MAX_STALE_TMP_FILES = 20;
|
|
9727
|
+
var HOST_NAME = hostname2();
|
|
9728
|
+
function lockOwnerStamp() {
|
|
9729
|
+
return `${HOST_NAME}:${process.pid}`;
|
|
9730
|
+
}
|
|
9731
|
+
async function maybeUnlinkOwnedLock(lockPath) {
|
|
9732
|
+
const owner = await fs11.readFile(lockPath, "utf8").catch(() => null);
|
|
9733
|
+
if (owner === null || owner.trim() !== lockOwnerStamp()) return;
|
|
9734
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
9735
|
+
try {
|
|
9736
|
+
await fs11.unlink(lockPath);
|
|
9737
|
+
return;
|
|
9738
|
+
} catch {
|
|
9739
|
+
if (attempt < 2) await new Promise((resolve11) => setTimeout(resolve11, 5));
|
|
9740
|
+
}
|
|
9741
|
+
}
|
|
9742
|
+
}
|
|
9625
9743
|
async function breakStaleLock(lockPath) {
|
|
9626
9744
|
try {
|
|
9627
|
-
const
|
|
9628
|
-
|
|
9629
|
-
|
|
9630
|
-
|
|
9631
|
-
|
|
9632
|
-
|
|
9633
|
-
|
|
9634
|
-
|
|
9635
|
-
|
|
9636
|
-
|
|
9637
|
-
|
|
9638
|
-
return
|
|
9745
|
+
const content = await fs11.readFile(lockPath, "utf8").catch(() => "");
|
|
9746
|
+
const trimmed = content.trim();
|
|
9747
|
+
const colonIdx = trimmed.indexOf(":");
|
|
9748
|
+
if (colonIdx === -1) {
|
|
9749
|
+
const barePid = Number.parseInt(trimmed, 10);
|
|
9750
|
+
if (Number.isInteger(barePid) && barePid > 0 && !isPidAlive(barePid)) {
|
|
9751
|
+
return await breakStaleLockVerified(lockPath, async () => {
|
|
9752
|
+
const reread = await fs11.readFile(lockPath, "utf8").catch(() => "");
|
|
9753
|
+
return reread.trim() === trimmed;
|
|
9754
|
+
});
|
|
9755
|
+
}
|
|
9756
|
+
return await breakStaleLockVerified(lockPath, async () => {
|
|
9757
|
+
const st = await fs11.stat(lockPath);
|
|
9758
|
+
return Date.now() - st.mtimeMs > STALE_LOCK_MS;
|
|
9759
|
+
});
|
|
9639
9760
|
}
|
|
9640
|
-
|
|
9761
|
+
const ownerHost = trimmed.slice(0, colonIdx);
|
|
9762
|
+
const ownerPid = Number.parseInt(trimmed.slice(colonIdx + 1), 10);
|
|
9763
|
+
if (ownerHost === HOST_NAME && Number.isInteger(ownerPid) && ownerPid > 0) {
|
|
9764
|
+
if (isPidAlive(ownerPid)) {
|
|
9765
|
+
const stat15 = await fs11.stat(lockPath);
|
|
9766
|
+
if (Date.now() - stat15.mtimeMs > SAME_HOST_STALE_MS) {
|
|
9767
|
+
return await breakStaleLockVerified(lockPath, async () => {
|
|
9768
|
+
const st = await fs11.stat(lockPath);
|
|
9769
|
+
return Date.now() - st.mtimeMs > SAME_HOST_STALE_MS;
|
|
9770
|
+
});
|
|
9771
|
+
}
|
|
9772
|
+
return false;
|
|
9773
|
+
}
|
|
9774
|
+
return await breakStaleLockVerified(lockPath, async () => {
|
|
9775
|
+
const reread = await fs11.readFile(lockPath, "utf8").catch(() => "");
|
|
9776
|
+
return reread.trim() === trimmed;
|
|
9777
|
+
});
|
|
9778
|
+
}
|
|
9779
|
+
return await breakStaleLockVerified(lockPath, async () => {
|
|
9780
|
+
const st = await fs11.stat(lockPath);
|
|
9781
|
+
return Date.now() - st.mtimeMs > STALE_LOCK_MS;
|
|
9782
|
+
});
|
|
9641
9783
|
} catch {
|
|
9642
9784
|
return true;
|
|
9643
9785
|
}
|
|
9644
9786
|
}
|
|
9787
|
+
async function breakStaleLockVerified(lockPath, verify) {
|
|
9788
|
+
if (!await verify()) return false;
|
|
9789
|
+
return await breakLockAtomically(lockPath) === true;
|
|
9790
|
+
}
|
|
9791
|
+
async function breakLockAtomically(lockPath) {
|
|
9792
|
+
const tombstone = `${lockPath}.stale-${randomUUID5()}.tmp`;
|
|
9793
|
+
try {
|
|
9794
|
+
await fs11.rename(lockPath, tombstone);
|
|
9795
|
+
} catch {
|
|
9796
|
+
return false;
|
|
9797
|
+
}
|
|
9798
|
+
void fs11.unlink(tombstone).catch(() => void 0);
|
|
9799
|
+
return true;
|
|
9800
|
+
}
|
|
9645
9801
|
async function writeAtomicFile(filePath, registry) {
|
|
9646
9802
|
const tmp = path23.join(
|
|
9647
9803
|
path23.dirname(filePath),
|
|
@@ -10102,16 +10258,33 @@ var SessionRegistry = class {
|
|
|
10102
10258
|
continue;
|
|
10103
10259
|
}
|
|
10104
10260
|
}
|
|
10261
|
+
let stampFailed = false;
|
|
10105
10262
|
try {
|
|
10106
|
-
|
|
10263
|
+
let stamped = false;
|
|
10264
|
+
for (let stampAttempt = 0; stampAttempt < 3 && !stamped; stampAttempt++) {
|
|
10265
|
+
try {
|
|
10266
|
+
await lockHandle.writeFile(lockOwnerStamp());
|
|
10267
|
+
stamped = true;
|
|
10268
|
+
} catch {
|
|
10269
|
+
if (stampAttempt < 2) await new Promise((resolve11) => setTimeout(resolve11, 5));
|
|
10270
|
+
}
|
|
10271
|
+
}
|
|
10272
|
+
if (!stamped) {
|
|
10273
|
+
stampFailed = true;
|
|
10274
|
+
throw new Error("failed to stamp session-registry lock owner");
|
|
10275
|
+
}
|
|
10107
10276
|
const raw = await fs12.readFile(this.filePath, "utf8").catch(() => "{}");
|
|
10108
10277
|
const registry = parseRegistry(raw);
|
|
10109
10278
|
fn(registry);
|
|
10110
10279
|
await this.writeAtomicWithRetry(registry);
|
|
10111
10280
|
return;
|
|
10112
10281
|
} finally {
|
|
10113
|
-
await lockHandle.close();
|
|
10114
|
-
|
|
10282
|
+
await lockHandle.close().catch(() => void 0);
|
|
10283
|
+
if (stampFailed) {
|
|
10284
|
+
await fs12.unlink(lockPath).catch(() => void 0);
|
|
10285
|
+
} else {
|
|
10286
|
+
await maybeUnlinkOwnedLock(lockPath);
|
|
10287
|
+
}
|
|
10115
10288
|
}
|
|
10116
10289
|
} catch (err) {
|
|
10117
10290
|
if (err instanceof SessionOwnershipConflictError) throw err;
|
|
@@ -11915,7 +12088,7 @@ async function mutateTasks(filePath, sessionId, fn, events, traceId) {
|
|
|
11915
12088
|
|
|
11916
12089
|
// src/storage/director-state.ts
|
|
11917
12090
|
import * as fsp21 from "node:fs/promises";
|
|
11918
|
-
import { hostname as
|
|
12091
|
+
import { hostname as hostname3 } from "node:os";
|
|
11919
12092
|
async function loadDirectorState(filePath) {
|
|
11920
12093
|
let raw;
|
|
11921
12094
|
try {
|
|
@@ -11946,7 +12119,7 @@ async function acquireDirectorStateLock(lockPath, processId = process.pid) {
|
|
|
11946
12119
|
}
|
|
11947
12120
|
const lock = {
|
|
11948
12121
|
pid: processId,
|
|
11949
|
-
hostname:
|
|
12122
|
+
hostname: hostname3(),
|
|
11950
12123
|
startedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11951
12124
|
};
|
|
11952
12125
|
await atomicWrite(lockPath, JSON.stringify(lock), { mode: 384 });
|
|
@@ -13529,6 +13702,613 @@ function timeAgo(iso) {
|
|
|
13529
13702
|
return `${days}d ago`;
|
|
13530
13703
|
}
|
|
13531
13704
|
|
|
13705
|
+
// src/storage/cloud-config-sync.ts
|
|
13706
|
+
import { createHash as createHash9, randomUUID as nodeRandomUUID } from "node:crypto";
|
|
13707
|
+
import * as fs18 from "node:fs/promises";
|
|
13708
|
+
import * as path30 from "node:path";
|
|
13709
|
+
|
|
13710
|
+
// src/storage/cloud-config-sync/sanitize.ts
|
|
13711
|
+
var SAGE_TREE = {
|
|
13712
|
+
enabled: true,
|
|
13713
|
+
storage: { projectLocal: true, directory: true },
|
|
13714
|
+
inject: {
|
|
13715
|
+
turnContext: true,
|
|
13716
|
+
toolResults: true,
|
|
13717
|
+
taskAware: true,
|
|
13718
|
+
maxHintsPerTool: true,
|
|
13719
|
+
maxCharsPerTool: true,
|
|
13720
|
+
minScore: true,
|
|
13721
|
+
repeatCooldownMs: true,
|
|
13722
|
+
maxTurnMemories: true,
|
|
13723
|
+
maxCharsPerTurn: true,
|
|
13724
|
+
minImportance: true,
|
|
13725
|
+
relationFloor: true
|
|
13726
|
+
},
|
|
13727
|
+
hygiene: {
|
|
13728
|
+
autoAfterSession: true,
|
|
13729
|
+
autoOnFileChange: true,
|
|
13730
|
+
retentionDays: true,
|
|
13731
|
+
archiveLowConfidenceAfterDays: true
|
|
13732
|
+
},
|
|
13733
|
+
embeddings: { enabled: true }
|
|
13734
|
+
};
|
|
13735
|
+
var CORE_RUNTIME_TREE = {
|
|
13736
|
+
maxConcurrent: true,
|
|
13737
|
+
context: {
|
|
13738
|
+
mode: true,
|
|
13739
|
+
autoCompact: true,
|
|
13740
|
+
strategy: true,
|
|
13741
|
+
warnThreshold: true,
|
|
13742
|
+
softThreshold: true,
|
|
13743
|
+
hardThreshold: true,
|
|
13744
|
+
preserveK: true,
|
|
13745
|
+
eliseThreshold: true,
|
|
13746
|
+
targetLoad: true
|
|
13747
|
+
},
|
|
13748
|
+
tools: {
|
|
13749
|
+
defaultExecutionStrategy: true,
|
|
13750
|
+
maxIterations: true,
|
|
13751
|
+
iterationTimeoutMs: true,
|
|
13752
|
+
sessionTimeoutMs: true,
|
|
13753
|
+
perIterationOutputCapBytes: true,
|
|
13754
|
+
autoExtendLimit: true,
|
|
13755
|
+
restrictToProjectRoot: true,
|
|
13756
|
+
descriptionMode: true,
|
|
13757
|
+
disabledTools: true,
|
|
13758
|
+
maxToolTimeoutMs: true,
|
|
13759
|
+
loopDetection: {
|
|
13760
|
+
mode: true,
|
|
13761
|
+
steerThreshold: true,
|
|
13762
|
+
cutThreshold: true,
|
|
13763
|
+
windowSize: true,
|
|
13764
|
+
callRepeatThreshold: true
|
|
13765
|
+
}
|
|
13766
|
+
},
|
|
13767
|
+
log: { level: true, format: true },
|
|
13768
|
+
features: {
|
|
13769
|
+
mcp: true,
|
|
13770
|
+
plugins: true,
|
|
13771
|
+
memory: true,
|
|
13772
|
+
modelsRegistry: true,
|
|
13773
|
+
skills: true,
|
|
13774
|
+
prompts: true,
|
|
13775
|
+
tokenSavingMode: true,
|
|
13776
|
+
allowOutsideProjectRoot: true,
|
|
13777
|
+
developerMode: true
|
|
13778
|
+
},
|
|
13779
|
+
session: { auditLevel: true, sampling: { toolProgress: { sampleRate: true } } },
|
|
13780
|
+
indexing: { onSessionStart: true, onEdit: true, watchExternal: true, debounceMs: true },
|
|
13781
|
+
circuitBreaker: { enabled: true, autoKillResetMs: true },
|
|
13782
|
+
modelRuntime: {
|
|
13783
|
+
reasoning: { mode: true, effort: true, preserve: true },
|
|
13784
|
+
cache: { ttl: true }
|
|
13785
|
+
},
|
|
13786
|
+
systemPrompt: { variant: true },
|
|
13787
|
+
skills: { readClaudeSkills: true },
|
|
13788
|
+
Sage: SAGE_TREE,
|
|
13789
|
+
fallbackAuto: true,
|
|
13790
|
+
yolo: true,
|
|
13791
|
+
nextPrediction: true,
|
|
13792
|
+
hints: true,
|
|
13793
|
+
debugStream: true
|
|
13794
|
+
};
|
|
13795
|
+
var UI_PREFERENCES_TREE = {
|
|
13796
|
+
autonomy: {
|
|
13797
|
+
defaultMode: true,
|
|
13798
|
+
autoProceedDelayMs: true,
|
|
13799
|
+
autoProceedMaxIterations: true,
|
|
13800
|
+
autonomyNextPrompt: true,
|
|
13801
|
+
terminalTitleAnimation: true,
|
|
13802
|
+
yolo: true,
|
|
13803
|
+
streamFleet: true,
|
|
13804
|
+
chime: true,
|
|
13805
|
+
confirmExit: true,
|
|
13806
|
+
mouseMode: true,
|
|
13807
|
+
enhance: true,
|
|
13808
|
+
enhanceDelayMs: true,
|
|
13809
|
+
enhanceCountdownMs: true,
|
|
13810
|
+
enhanceLanguage: true,
|
|
13811
|
+
statuslineMode: true,
|
|
13812
|
+
thinkingWord: true,
|
|
13813
|
+
midRunSendPicker: true,
|
|
13814
|
+
animationStyle: true,
|
|
13815
|
+
fleetChatVerbosity: true,
|
|
13816
|
+
refinerFallbackProfile: true,
|
|
13817
|
+
refinerProvider: true,
|
|
13818
|
+
refinerModel: true,
|
|
13819
|
+
showModelReasoning: true,
|
|
13820
|
+
shellBangWarningDontShowAgain: true,
|
|
13821
|
+
showAgentSwarmPanel: true,
|
|
13822
|
+
showSageMemoryInject: true,
|
|
13823
|
+
readAdvancedMode: true,
|
|
13824
|
+
panelPositions: true
|
|
13825
|
+
},
|
|
13826
|
+
launch: { mode: true, autonomy: true },
|
|
13827
|
+
uiLocale: true
|
|
13828
|
+
};
|
|
13829
|
+
var MODELS_ROUTING_TREE = {
|
|
13830
|
+
provider: true,
|
|
13831
|
+
model: true,
|
|
13832
|
+
favoriteModels: true,
|
|
13833
|
+
favoriteModelsOnly: true,
|
|
13834
|
+
fallbackModels: true,
|
|
13835
|
+
fallbackProfiles: true,
|
|
13836
|
+
modelMatrix: { "*": { fallbackProfile: true } },
|
|
13837
|
+
brain: {
|
|
13838
|
+
mode: true,
|
|
13839
|
+
maxAutoRisk: true,
|
|
13840
|
+
models: true,
|
|
13841
|
+
strategy: true,
|
|
13842
|
+
decisionTimeoutMs: true,
|
|
13843
|
+
humanTimeoutMs: true,
|
|
13844
|
+
council: {
|
|
13845
|
+
enabled: true,
|
|
13846
|
+
minRisk: true,
|
|
13847
|
+
voters: true
|
|
13848
|
+
},
|
|
13849
|
+
ledger: { enabled: true, autoDenyAfterFailures: true },
|
|
13850
|
+
trace: { enabled: true }
|
|
13851
|
+
}
|
|
13852
|
+
};
|
|
13853
|
+
var PROVIDERS_CATALOG_TREE = {
|
|
13854
|
+
providers: {
|
|
13855
|
+
"*": {
|
|
13856
|
+
type: true,
|
|
13857
|
+
family: true,
|
|
13858
|
+
baseUrl: true,
|
|
13859
|
+
envVars: true,
|
|
13860
|
+
models: true,
|
|
13861
|
+
activeKey: true
|
|
13862
|
+
}
|
|
13863
|
+
}
|
|
13864
|
+
};
|
|
13865
|
+
var MCP_SERVERS_TREE = {
|
|
13866
|
+
mcpServers: {
|
|
13867
|
+
"*": {
|
|
13868
|
+
name: true,
|
|
13869
|
+
transport: true,
|
|
13870
|
+
command: true,
|
|
13871
|
+
args: true,
|
|
13872
|
+
url: true,
|
|
13873
|
+
description: true,
|
|
13874
|
+
permission: true,
|
|
13875
|
+
enabled: true,
|
|
13876
|
+
lazy: true,
|
|
13877
|
+
envVars: true
|
|
13878
|
+
}
|
|
13879
|
+
}
|
|
13880
|
+
};
|
|
13881
|
+
var EXTENSIONS_PLUGINS_TREE = {
|
|
13882
|
+
plugins: true,
|
|
13883
|
+
extensions: true
|
|
13884
|
+
};
|
|
13885
|
+
var CLOUD_SYNC_CONTRACT = {
|
|
13886
|
+
"core.runtime": CORE_RUNTIME_TREE,
|
|
13887
|
+
"ui.preferences": UI_PREFERENCES_TREE,
|
|
13888
|
+
"models.routing": MODELS_ROUTING_TREE,
|
|
13889
|
+
"providers.catalog": PROVIDERS_CATALOG_TREE,
|
|
13890
|
+
"mcp.servers": MCP_SERVERS_TREE,
|
|
13891
|
+
"extensions.plugins": EXTENSIONS_PLUGINS_TREE
|
|
13892
|
+
};
|
|
13893
|
+
var CLOUD_SYNC_NAMESPACES = Object.keys(CLOUD_SYNC_CONTRACT);
|
|
13894
|
+
var NAMESPACE_SCHEMA_VERSIONS = {
|
|
13895
|
+
"core.runtime": 1,
|
|
13896
|
+
"ui.preferences": 1,
|
|
13897
|
+
"models.routing": 1,
|
|
13898
|
+
"providers.catalog": 1,
|
|
13899
|
+
"mcp.servers": 2,
|
|
13900
|
+
"extensions.plugins": 1
|
|
13901
|
+
};
|
|
13902
|
+
var LOCAL_ONLY_TOP_LEVEL = [
|
|
13903
|
+
"version",
|
|
13904
|
+
"configScope",
|
|
13905
|
+
"hq",
|
|
13906
|
+
"cloudSync",
|
|
13907
|
+
"sync",
|
|
13908
|
+
"models",
|
|
13909
|
+
"apiKey",
|
|
13910
|
+
"baseUrl"
|
|
13911
|
+
];
|
|
13912
|
+
var ENCRYPTED_VALUE = /enc:v\d+:/;
|
|
13913
|
+
var SECRET_KEY = /^(api[-_]?key|apikeys|.*token|secret.*|.*secret|password|passwd|credential.*|private[-_]?key|authorization)$/i;
|
|
13914
|
+
function isPlainObject(value) {
|
|
13915
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
13916
|
+
}
|
|
13917
|
+
function stripSecretMaterial(value) {
|
|
13918
|
+
if (typeof value === "string") {
|
|
13919
|
+
return ENCRYPTED_VALUE.test(value) ? void 0 : value;
|
|
13920
|
+
}
|
|
13921
|
+
if (Array.isArray(value)) {
|
|
13922
|
+
const cleaned = value.map((entry) => stripSecretMaterial(entry));
|
|
13923
|
+
return cleaned.filter((entry) => entry !== void 0);
|
|
13924
|
+
}
|
|
13925
|
+
if (isPlainObject(value)) {
|
|
13926
|
+
const out = {};
|
|
13927
|
+
for (const [key, child] of Object.entries(value)) {
|
|
13928
|
+
if (SECRET_KEY.test(key)) continue;
|
|
13929
|
+
const cleaned = stripSecretMaterial(child);
|
|
13930
|
+
if (cleaned !== void 0) out[key] = cleaned;
|
|
13931
|
+
}
|
|
13932
|
+
return out;
|
|
13933
|
+
}
|
|
13934
|
+
return value;
|
|
13935
|
+
}
|
|
13936
|
+
function project(value, tree) {
|
|
13937
|
+
if (tree === true) return value;
|
|
13938
|
+
if (!isPlainObject(value)) return void 0;
|
|
13939
|
+
const wildcard = tree["*"];
|
|
13940
|
+
const out = {};
|
|
13941
|
+
for (const [key, child] of Object.entries(value)) {
|
|
13942
|
+
const node = Object.hasOwn(tree, key) && key !== "*" ? tree[key] : wildcard;
|
|
13943
|
+
if (node === void 0) continue;
|
|
13944
|
+
const projected = project(child, node);
|
|
13945
|
+
if (projected !== void 0) out[key] = projected;
|
|
13946
|
+
}
|
|
13947
|
+
return out;
|
|
13948
|
+
}
|
|
13949
|
+
function buildNamespacePayloads(config) {
|
|
13950
|
+
const payloads = {};
|
|
13951
|
+
for (const [namespace, tree] of Object.entries(CLOUD_SYNC_CONTRACT)) {
|
|
13952
|
+
if (tree === true) continue;
|
|
13953
|
+
const payload = {};
|
|
13954
|
+
for (const [key, node] of Object.entries(tree)) {
|
|
13955
|
+
if (!(key in config) || config[key] === void 0) continue;
|
|
13956
|
+
const projected = project(config[key], node);
|
|
13957
|
+
if (projected === void 0) continue;
|
|
13958
|
+
const cleaned = stripSecretMaterial(projected);
|
|
13959
|
+
if (cleaned === void 0) continue;
|
|
13960
|
+
if (isPlainObject(cleaned) && Object.keys(cleaned).length === 0 && node !== true) continue;
|
|
13961
|
+
payload[key] = cleaned;
|
|
13962
|
+
}
|
|
13963
|
+
if (Object.keys(payload).length > 0) payloads[namespace] = payload;
|
|
13964
|
+
}
|
|
13965
|
+
return payloads;
|
|
13966
|
+
}
|
|
13967
|
+
function reinjectLocalSecrets(local, incoming) {
|
|
13968
|
+
if (!isPlainObject(local) || !isPlainObject(incoming)) return incoming;
|
|
13969
|
+
const out = { ...incoming };
|
|
13970
|
+
for (const [key, value] of Object.entries(local)) {
|
|
13971
|
+
const secretish = SECRET_KEY.test(key) || typeof value === "string" && ENCRYPTED_VALUE.test(value);
|
|
13972
|
+
if (secretish && !(key in incoming)) {
|
|
13973
|
+
out[key] = value;
|
|
13974
|
+
continue;
|
|
13975
|
+
}
|
|
13976
|
+
if (isPlainObject(value) && isPlainObject(incoming[key])) {
|
|
13977
|
+
out[key] = reinjectLocalSecrets(value, incoming[key]);
|
|
13978
|
+
}
|
|
13979
|
+
}
|
|
13980
|
+
return out;
|
|
13981
|
+
}
|
|
13982
|
+
function mergeAtContract(local, incoming, tree) {
|
|
13983
|
+
if (tree === true) return reinjectLocalSecrets(local, incoming);
|
|
13984
|
+
if (!isPlainObject(incoming)) return local;
|
|
13985
|
+
const base = isPlainObject(local) ? { ...local } : {};
|
|
13986
|
+
const wildcard = tree["*"];
|
|
13987
|
+
for (const [key, child] of Object.entries(incoming)) {
|
|
13988
|
+
const node = Object.hasOwn(tree, key) && key !== "*" ? tree[key] : wildcard;
|
|
13989
|
+
if (node === void 0) continue;
|
|
13990
|
+
base[key] = mergeAtContract(base[key], child, node);
|
|
13991
|
+
}
|
|
13992
|
+
return base;
|
|
13993
|
+
}
|
|
13994
|
+
function applyNamespacePayload(config, namespace, payload) {
|
|
13995
|
+
const tree = CLOUD_SYNC_CONTRACT[namespace];
|
|
13996
|
+
if (!tree || tree === true) return config;
|
|
13997
|
+
const next = { ...config };
|
|
13998
|
+
for (const [key, incoming] of Object.entries(payload)) {
|
|
13999
|
+
const node = Object.hasOwn(tree, key) ? tree[key] : void 0;
|
|
14000
|
+
if (node === void 0) continue;
|
|
14001
|
+
next[key] = mergeAtContract(next[key], incoming, node);
|
|
14002
|
+
}
|
|
14003
|
+
return next;
|
|
14004
|
+
}
|
|
14005
|
+
|
|
14006
|
+
// src/storage/cloud-config-sync.ts
|
|
14007
|
+
var EMPTY_STATE = {
|
|
14008
|
+
version: 1,
|
|
14009
|
+
cursor: 0,
|
|
14010
|
+
namespaceRevisions: {},
|
|
14011
|
+
syncedHashes: {},
|
|
14012
|
+
syncedPayloads: {},
|
|
14013
|
+
pendingMutations: {},
|
|
14014
|
+
rejectedHashes: {}
|
|
14015
|
+
};
|
|
14016
|
+
function stableStringify3(value) {
|
|
14017
|
+
if (Array.isArray(value)) {
|
|
14018
|
+
return `[${value.map((entry) => stableStringify3(entry)).join(",")}]`;
|
|
14019
|
+
}
|
|
14020
|
+
if (typeof value === "object" && value !== null) {
|
|
14021
|
+
const entries = Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${stableStringify3(v)}`);
|
|
14022
|
+
return `{${entries.join(",")}}`;
|
|
14023
|
+
}
|
|
14024
|
+
return JSON.stringify(value) ?? "null";
|
|
14025
|
+
}
|
|
14026
|
+
function hashPayload(payload) {
|
|
14027
|
+
return createHash9("sha256").update(stableStringify3(payload)).digest("hex");
|
|
14028
|
+
}
|
|
14029
|
+
function deepEquals(a, b) {
|
|
14030
|
+
return stableStringify3(a) === stableStringify3(b);
|
|
14031
|
+
}
|
|
14032
|
+
var CloudConfigSync = class {
|
|
14033
|
+
constructor(deps) {
|
|
14034
|
+
this.deps = deps;
|
|
14035
|
+
this.fetchImpl = deps.fetchImpl ?? fetch;
|
|
14036
|
+
this.uuid = deps.randomUUID ?? (() => nodeRandomUUID());
|
|
14037
|
+
this.timeoutMs = deps.requestTimeoutMs ?? 15e3;
|
|
14038
|
+
}
|
|
14039
|
+
deps;
|
|
14040
|
+
state = null;
|
|
14041
|
+
fetchImpl;
|
|
14042
|
+
uuid;
|
|
14043
|
+
timeoutMs;
|
|
14044
|
+
/** One full pull-then-push-then-heartbeat pass. Never throws; failures are summarized. */
|
|
14045
|
+
async syncOnce() {
|
|
14046
|
+
const settings = this.deps.getSettings();
|
|
14047
|
+
if (!settings?.enabled || !settings.url || !settings.token) {
|
|
14048
|
+
return summary({ skipped: true, message: "Cloud sync is not configured." });
|
|
14049
|
+
}
|
|
14050
|
+
const state = await this.loadState();
|
|
14051
|
+
const pulled = [];
|
|
14052
|
+
const pushed = [];
|
|
14053
|
+
const conflicted = [];
|
|
14054
|
+
const rejected = [];
|
|
14055
|
+
try {
|
|
14056
|
+
await this.pullChanges(settings, state, pulled);
|
|
14057
|
+
await this.pushDirty(settings, state, { pushed, conflicted, rejected });
|
|
14058
|
+
await this.heartbeat(settings, state);
|
|
14059
|
+
state.lastSyncedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
14060
|
+
await this.saveState(state);
|
|
14061
|
+
return summary({
|
|
14062
|
+
ok: true,
|
|
14063
|
+
pulled,
|
|
14064
|
+
pushed,
|
|
14065
|
+
conflicted,
|
|
14066
|
+
rejected,
|
|
14067
|
+
message: pulled.length + pushed.length === 0 ? "Already in sync." : `Pulled ${pulled.length}, pushed ${pushed.length} namespace(s).`
|
|
14068
|
+
});
|
|
14069
|
+
} catch (error) {
|
|
14070
|
+
await this.saveState(state).catch(() => {
|
|
14071
|
+
});
|
|
14072
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
14073
|
+
this.deps.logger?.warn(`cloud-config-sync: pass failed: ${message}`);
|
|
14074
|
+
return summary({ pulled, pushed, conflicted, rejected, message });
|
|
14075
|
+
}
|
|
14076
|
+
}
|
|
14077
|
+
/** Human-readable status for the slash command. */
|
|
14078
|
+
async statusText() {
|
|
14079
|
+
const settings = this.deps.getSettings();
|
|
14080
|
+
if (!settings?.enabled) {
|
|
14081
|
+
return "Cloud config sync: disabled. Run `/cloudsync on` after setting url + token.";
|
|
14082
|
+
}
|
|
14083
|
+
const state = await this.loadState();
|
|
14084
|
+
const dirty = await this.dirtyNamespaces(state);
|
|
14085
|
+
const rejectedList = Object.keys(state.rejectedHashes);
|
|
14086
|
+
const lines = [
|
|
14087
|
+
"Cloud config sync: enabled",
|
|
14088
|
+
` url: ${settings.url}`,
|
|
14089
|
+
` cursor: ${state.cursor}`,
|
|
14090
|
+
` last sync: ${state.lastSyncedAt ?? "never"}`,
|
|
14091
|
+
` dirty: ${dirty.length > 0 ? dirty.join(", ") : "none"}`
|
|
14092
|
+
];
|
|
14093
|
+
if (rejectedList.length > 0) {
|
|
14094
|
+
lines.push(` rejected: ${rejectedList.join(", ")} (kept local; edit to retry)`);
|
|
14095
|
+
}
|
|
14096
|
+
return lines.join("\n");
|
|
14097
|
+
}
|
|
14098
|
+
// ── Pull ────────────────────────────────────────────────────────────────
|
|
14099
|
+
async pullChanges(settings, state, pulled) {
|
|
14100
|
+
const delta = (await this.request(settings, "GET", `/api/sync/v1/config?since=${state.cursor}`)).json;
|
|
14101
|
+
const toRead = delta.full ? CLOUD_SYNC_NAMESPACES.map((namespace) => ({ namespace })) : delta.changed.filter(
|
|
14102
|
+
(entry) => CLOUD_SYNC_NAMESPACES.includes(entry.namespace) && entry.revision > (state.namespaceRevisions[entry.namespace] ?? 0)
|
|
14103
|
+
);
|
|
14104
|
+
const dirtyBefore = new Set(await this.dirtyNamespaces(state));
|
|
14105
|
+
for (const entry of toRead) {
|
|
14106
|
+
const snapshot = (await this.request(
|
|
14107
|
+
settings,
|
|
14108
|
+
"GET",
|
|
14109
|
+
`/api/sync/v1/config/${encodeURIComponent(entry.namespace)}`
|
|
14110
|
+
)).json;
|
|
14111
|
+
if ((state.namespaceRevisions[entry.namespace] ?? 0) >= snapshot.revision) continue;
|
|
14112
|
+
if (snapshot.revision === 0) {
|
|
14113
|
+
state.namespaceRevisions[entry.namespace] = 0;
|
|
14114
|
+
continue;
|
|
14115
|
+
}
|
|
14116
|
+
if (dirtyBefore.has(entry.namespace)) {
|
|
14117
|
+
const local = await this.deps.readLocalConfig();
|
|
14118
|
+
const localPayload = buildNamespacePayloads(local)[entry.namespace] ?? {};
|
|
14119
|
+
const base = state.syncedPayloads[entry.namespace] ?? {};
|
|
14120
|
+
const merged = { ...snapshot.payload };
|
|
14121
|
+
for (const [key, localValue] of Object.entries(localPayload)) {
|
|
14122
|
+
if (!deepEquals(localValue, base[key])) merged[key] = localValue;
|
|
14123
|
+
}
|
|
14124
|
+
await this.deps.writeLocalConfig(
|
|
14125
|
+
(config) => applyNamespacePayload(config, entry.namespace, merged)
|
|
14126
|
+
);
|
|
14127
|
+
state.namespaceRevisions[entry.namespace] = snapshot.revision;
|
|
14128
|
+
state.syncedPayloads[entry.namespace] = snapshot.payload;
|
|
14129
|
+
} else {
|
|
14130
|
+
await this.applySnapshot(entry.namespace, snapshot, state);
|
|
14131
|
+
}
|
|
14132
|
+
pulled.push(entry.namespace);
|
|
14133
|
+
}
|
|
14134
|
+
state.cursor = Math.max(state.cursor, delta.cursor);
|
|
14135
|
+
}
|
|
14136
|
+
async applySnapshot(namespace, snapshot, state) {
|
|
14137
|
+
await this.deps.writeLocalConfig(
|
|
14138
|
+
(config) => applyNamespacePayload(config, namespace, snapshot.payload)
|
|
14139
|
+
);
|
|
14140
|
+
state.namespaceRevisions[namespace] = snapshot.revision;
|
|
14141
|
+
state.syncedPayloads[namespace] = snapshot.payload;
|
|
14142
|
+
const local = await this.deps.readLocalConfig();
|
|
14143
|
+
const projected = buildNamespacePayloads(local)[namespace] ?? {};
|
|
14144
|
+
state.syncedHashes[namespace] = hashPayload(projected);
|
|
14145
|
+
delete state.pendingMutations[namespace];
|
|
14146
|
+
delete state.rejectedHashes[namespace];
|
|
14147
|
+
}
|
|
14148
|
+
// ── Push ────────────────────────────────────────────────────────────────
|
|
14149
|
+
async dirtyNamespaces(state) {
|
|
14150
|
+
const local = await this.deps.readLocalConfig();
|
|
14151
|
+
const payloads = buildNamespacePayloads(local);
|
|
14152
|
+
return Object.entries(payloads).filter(([namespace, payload]) => hashPayload(payload) !== state.syncedHashes[namespace]).map(([namespace]) => namespace);
|
|
14153
|
+
}
|
|
14154
|
+
async pushDirty(settings, state, out) {
|
|
14155
|
+
const local = await this.deps.readLocalConfig();
|
|
14156
|
+
const payloads = buildNamespacePayloads(local);
|
|
14157
|
+
for (const [namespace, payload] of Object.entries(payloads)) {
|
|
14158
|
+
const hash = hashPayload(payload);
|
|
14159
|
+
if (hash === state.syncedHashes[namespace]) continue;
|
|
14160
|
+
if (state.rejectedHashes[namespace] === hash) continue;
|
|
14161
|
+
const pending = state.pendingMutations[namespace];
|
|
14162
|
+
const mutationId = pending?.hash === hash ? pending.mutationId : this.uuid();
|
|
14163
|
+
state.pendingMutations[namespace] = { mutationId, hash };
|
|
14164
|
+
await this.saveState(state);
|
|
14165
|
+
const result = await this.putNamespace(settings, state, namespace, payload, mutationId);
|
|
14166
|
+
if (result.kind === "applied") {
|
|
14167
|
+
out.pushed.push(namespace);
|
|
14168
|
+
} else if (result.kind === "conflict") {
|
|
14169
|
+
out.conflicted.push(namespace);
|
|
14170
|
+
await this.resolveConflict(settings, state, namespace, payload, result.server, out);
|
|
14171
|
+
} else {
|
|
14172
|
+
state.rejectedHashes[namespace] = hash;
|
|
14173
|
+
delete state.pendingMutations[namespace];
|
|
14174
|
+
out.rejected.push(namespace);
|
|
14175
|
+
this.deps.logger?.warn(
|
|
14176
|
+
`cloud-config-sync: server rejected ${namespace} (${result.code}); keeping it local until the config changes.`
|
|
14177
|
+
);
|
|
14178
|
+
}
|
|
14179
|
+
}
|
|
14180
|
+
}
|
|
14181
|
+
async putNamespace(settings, state, namespace, payload, mutationId) {
|
|
14182
|
+
const response = await this.request(
|
|
14183
|
+
settings,
|
|
14184
|
+
"PUT",
|
|
14185
|
+
`/api/sync/v1/config/${encodeURIComponent(namespace)}`,
|
|
14186
|
+
{
|
|
14187
|
+
baseRevision: state.namespaceRevisions[namespace] ?? 0,
|
|
14188
|
+
mutationId,
|
|
14189
|
+
schemaVersion: NAMESPACE_SCHEMA_VERSIONS[namespace] ?? 1,
|
|
14190
|
+
payload
|
|
14191
|
+
},
|
|
14192
|
+
// 409 (conflict) and 422 (contract mismatch) are protocol answers, not failures.
|
|
14193
|
+
[409, 422]
|
|
14194
|
+
);
|
|
14195
|
+
if (response.status === 422) {
|
|
14196
|
+
const code = response.json.error?.code;
|
|
14197
|
+
return { kind: "rejected", code: code ?? "invalid_payload" };
|
|
14198
|
+
}
|
|
14199
|
+
const body = response.json;
|
|
14200
|
+
if (response.status === 409) {
|
|
14201
|
+
return { kind: "conflict", server: body };
|
|
14202
|
+
}
|
|
14203
|
+
state.namespaceRevisions[namespace] = body.revision;
|
|
14204
|
+
state.syncedHashes[namespace] = hashPayload(payload);
|
|
14205
|
+
state.syncedPayloads[namespace] = payload;
|
|
14206
|
+
if (typeof body.cursor === "number") state.cursor = Math.max(state.cursor, body.cursor);
|
|
14207
|
+
delete state.pendingMutations[namespace];
|
|
14208
|
+
delete state.rejectedHashes[namespace];
|
|
14209
|
+
return { kind: "applied" };
|
|
14210
|
+
}
|
|
14211
|
+
/**
|
|
14212
|
+
* Three-way merge at owned-top-level-key granularity: keys unchanged locally since the
|
|
14213
|
+
* last sync adopt the server value; locally-changed keys win and are re-pushed on top
|
|
14214
|
+
* of the server revision with a fresh mutation id.
|
|
14215
|
+
*/
|
|
14216
|
+
async resolveConflict(settings, state, namespace, localPayload, server, out) {
|
|
14217
|
+
const base = state.syncedPayloads[namespace] ?? {};
|
|
14218
|
+
const merged = { ...server.payload };
|
|
14219
|
+
for (const [key, localValue] of Object.entries(localPayload)) {
|
|
14220
|
+
if (!deepEquals(localValue, base[key])) merged[key] = localValue;
|
|
14221
|
+
}
|
|
14222
|
+
await this.deps.writeLocalConfig((config) => applyNamespacePayload(config, namespace, merged));
|
|
14223
|
+
state.namespaceRevisions[namespace] = server.revision;
|
|
14224
|
+
const mutationId = this.uuid();
|
|
14225
|
+
const hash = hashPayload(merged);
|
|
14226
|
+
state.pendingMutations[namespace] = { mutationId, hash };
|
|
14227
|
+
await this.saveState(state);
|
|
14228
|
+
const result = await this.putNamespace(settings, state, namespace, merged, mutationId);
|
|
14229
|
+
if (result.kind === "applied") {
|
|
14230
|
+
out.pushed.push(namespace);
|
|
14231
|
+
} else {
|
|
14232
|
+
this.deps.logger?.warn(
|
|
14233
|
+
`cloud-config-sync: conflict resolution for ${namespace} did not converge (${result.kind}); will retry next pass.`
|
|
14234
|
+
);
|
|
14235
|
+
}
|
|
14236
|
+
}
|
|
14237
|
+
// ── Heartbeat ───────────────────────────────────────────────────────────
|
|
14238
|
+
async heartbeat(settings, state) {
|
|
14239
|
+
await this.request(settings, "POST", "/api/sync/v1/heartbeat", {
|
|
14240
|
+
transport: "poll",
|
|
14241
|
+
syncCursor: state.cursor,
|
|
14242
|
+
namespaceRevisions: state.namespaceRevisions
|
|
14243
|
+
}).catch((error) => {
|
|
14244
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
14245
|
+
this.deps.logger?.warn(`cloud-config-sync: heartbeat failed: ${message}`);
|
|
14246
|
+
});
|
|
14247
|
+
}
|
|
14248
|
+
// ── HTTP + state plumbing ───────────────────────────────────────────────
|
|
14249
|
+
async request(settings, method, pathname, body, acceptStatuses = []) {
|
|
14250
|
+
const url = `${settings.url.replace(/\/+$/, "")}${pathname}`;
|
|
14251
|
+
const headers = {
|
|
14252
|
+
Authorization: `Bearer ${settings.token}`,
|
|
14253
|
+
"X-WrongStack-Version": this.deps.appVersion ?? "unknown"
|
|
14254
|
+
};
|
|
14255
|
+
if (body !== void 0) headers["Content-Type"] = "application/json";
|
|
14256
|
+
const init = {
|
|
14257
|
+
method,
|
|
14258
|
+
headers,
|
|
14259
|
+
signal: AbortSignal.timeout(this.timeoutMs)
|
|
14260
|
+
};
|
|
14261
|
+
if (body !== void 0) init.body = JSON.stringify(body);
|
|
14262
|
+
const response = await this.fetchImpl(url, init);
|
|
14263
|
+
const ok = response.ok || acceptStatuses.includes(response.status);
|
|
14264
|
+
if (!ok) {
|
|
14265
|
+
if (response.status === 401) {
|
|
14266
|
+
throw new Error("unauthorized: the machine token is invalid, expired, or revoked.");
|
|
14267
|
+
}
|
|
14268
|
+
if (response.status === 429) {
|
|
14269
|
+
throw new Error("rate limited by the portal; backing off until the next pass.");
|
|
14270
|
+
}
|
|
14271
|
+
throw new Error(`${method} ${pathname} failed with HTTP ${response.status}.`);
|
|
14272
|
+
}
|
|
14273
|
+
const json = response.status === 204 ? null : await response.json().catch(() => null);
|
|
14274
|
+
return { status: response.status, json };
|
|
14275
|
+
}
|
|
14276
|
+
async loadState() {
|
|
14277
|
+
if (this.state) return this.state;
|
|
14278
|
+
try {
|
|
14279
|
+
const raw = await fs18.readFile(this.deps.statePath, "utf8");
|
|
14280
|
+
const parsed = JSON.parse(raw);
|
|
14281
|
+
this.state = {
|
|
14282
|
+
...structuredClone(EMPTY_STATE),
|
|
14283
|
+
...parsed,
|
|
14284
|
+
version: 1
|
|
14285
|
+
};
|
|
14286
|
+
} catch {
|
|
14287
|
+
this.state = structuredClone(EMPTY_STATE);
|
|
14288
|
+
}
|
|
14289
|
+
return this.state;
|
|
14290
|
+
}
|
|
14291
|
+
async saveState(state) {
|
|
14292
|
+
this.state = state;
|
|
14293
|
+
await fs18.mkdir(path30.dirname(this.deps.statePath), { recursive: true });
|
|
14294
|
+
await atomicWrite(this.deps.statePath, `${JSON.stringify(state, null, 2)}
|
|
14295
|
+
`, {
|
|
14296
|
+
mode: 384
|
|
14297
|
+
});
|
|
14298
|
+
}
|
|
14299
|
+
};
|
|
14300
|
+
function summary(partial) {
|
|
14301
|
+
return {
|
|
14302
|
+
ok: partial.ok ?? false,
|
|
14303
|
+
skipped: partial.skipped ?? false,
|
|
14304
|
+
pulled: partial.pulled ?? [],
|
|
14305
|
+
pushed: partial.pushed ?? [],
|
|
14306
|
+
conflicted: partial.conflicted ?? [],
|
|
14307
|
+
rejected: partial.rejected ?? [],
|
|
14308
|
+
message: partial.message
|
|
14309
|
+
};
|
|
14310
|
+
}
|
|
14311
|
+
|
|
13532
14312
|
// src/storage/session-event-bridge.ts
|
|
13533
14313
|
var CORE_RECONSTRUCT_EVENTS = /* @__PURE__ */ new Set([
|
|
13534
14314
|
"session_start",
|
|
@@ -13669,8 +14449,11 @@ export {
|
|
|
13669
14449
|
ALL_SYNC_CATEGORIES,
|
|
13670
14450
|
AgentStatusTracker,
|
|
13671
14451
|
AnnotationsStore,
|
|
14452
|
+
CLOUD_SYNC_CONTRACT,
|
|
14453
|
+
CLOUD_SYNC_NAMESPACES,
|
|
13672
14454
|
CONFIG_BEHAVIOR_DEFAULTS,
|
|
13673
14455
|
CORE_RECONSTRUCT_EVENTS,
|
|
14456
|
+
CloudConfigSync,
|
|
13674
14457
|
CloudSync,
|
|
13675
14458
|
ConfigMigrationError,
|
|
13676
14459
|
DEFAULT_CONFIG_MIGRATIONS,
|
|
@@ -13687,8 +14470,10 @@ export {
|
|
|
13687
14470
|
GraphMemoryBackend,
|
|
13688
14471
|
INPUT_HISTORY_DEFAULT_MAX,
|
|
13689
14472
|
InputHistoryStore,
|
|
14473
|
+
LOCAL_ONLY_TOP_LEVEL,
|
|
13690
14474
|
MAX_JOURNAL_ENTRIES,
|
|
13691
14475
|
MAX_PROGRESS_HISTORY,
|
|
14476
|
+
NAMESPACE_SCHEMA_VERSIONS,
|
|
13692
14477
|
PromptUsageStore,
|
|
13693
14478
|
QUEUE_MAX_BYTES,
|
|
13694
14479
|
QUEUE_MAX_ITEMS,
|
|
@@ -13706,9 +14491,11 @@ export {
|
|
|
13706
14491
|
addPlanItem,
|
|
13707
14492
|
appendJournal,
|
|
13708
14493
|
applyGoalDeliverableCompletions,
|
|
14494
|
+
applyNamespacePayload,
|
|
13709
14495
|
applyRewindToConversation,
|
|
13710
14496
|
attachPlanCheckpoint,
|
|
13711
14497
|
attachTodosCheckpoint,
|
|
14498
|
+
buildNamespacePayloads,
|
|
13712
14499
|
clearPlan,
|
|
13713
14500
|
coordinateGoalIteration,
|
|
13714
14501
|
createGoalKanbanBoard,
|
|
@@ -13770,6 +14557,7 @@ export {
|
|
|
13770
14557
|
setPlanItemStatus,
|
|
13771
14558
|
setProgress,
|
|
13772
14559
|
stripGoalDeliverableMarker,
|
|
14560
|
+
stripSecretMaterial,
|
|
13773
14561
|
summarizeUsage,
|
|
13774
14562
|
watchProviderConfig
|
|
13775
14563
|
};
|