@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
|
@@ -15,6 +15,8 @@ export interface AutoReviewConfig {
|
|
|
15
15
|
* omitted), and the remaining entries form the fallback chain.
|
|
16
16
|
*/
|
|
17
17
|
fallbackProfile?: string | undefined;
|
|
18
|
+
/** How each review chooses its starting model from the resolved profile pool. */
|
|
19
|
+
modelSelection?: 'round-robin' | 'random' | undefined;
|
|
18
20
|
/** Debounce window in ms — wait for quiet before firing review (default 15000). */
|
|
19
21
|
debounceMs?: number | undefined;
|
|
20
22
|
/** Max files per review batch (default 15). */
|
|
@@ -40,6 +42,7 @@ export interface ResolvedAutoReviewConfig {
|
|
|
40
42
|
provider: string;
|
|
41
43
|
model: string;
|
|
42
44
|
fallbackModels: string[];
|
|
45
|
+
modelSelection: 'round-robin' | 'random';
|
|
43
46
|
debounceMs: number;
|
|
44
47
|
maxFilesPerBatch: number;
|
|
45
48
|
maxConcurrentReviews: number;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ConfigStore } from '../types/config.js';
|
|
2
|
+
import type { Plugin } from '../types/plugin.js';
|
|
3
|
+
import type { SecretVault } from '../types/secret-vault.js';
|
|
4
|
+
import type { WstackPaths } from '../utils/wstack-paths.js';
|
|
5
|
+
interface CloudConfigSyncPluginOptions {
|
|
6
|
+
paths?: WstackPaths | undefined;
|
|
7
|
+
configStore?: ConfigStore | undefined;
|
|
8
|
+
vault?: SecretVault | undefined;
|
|
9
|
+
appVersion?: string | undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* CloudConfigSyncPlugin — background config synchronization with my.wrongstack.com.
|
|
13
|
+
*
|
|
14
|
+
* Registers `/cloudsync` and, while enabled, runs a pull-then-push pass every
|
|
15
|
+
* `cloudSync.intervalSeconds` (default 300). Distinct from `wstack-sync` (the legacy
|
|
16
|
+
* GitHub category sync): this speaks the portal's namespaced, schema-validated,
|
|
17
|
+
* secret-free sync API v1 and never uploads credential material — the sanitizer in
|
|
18
|
+
* `storage/cloud-config-sync/sanitize.ts` is the client half of the server's contract.
|
|
19
|
+
*/
|
|
20
|
+
export declare function createCloudConfigSyncPlugin(opts?: CloudConfigSyncPluginOptions): Plugin;
|
|
21
|
+
export {};
|
|
22
|
+
//# sourceMappingURL=cloud-config-sync-plugin.d.ts.map
|
|
@@ -1,24 +1,39 @@
|
|
|
1
1
|
import type { EventBus } from '../kernel/events.js';
|
|
2
2
|
import type { PluginAPI } from '../types/plugin.js';
|
|
3
3
|
import type { ReviewContextBundle } from './review-types.js';
|
|
4
|
+
export interface ClaimStoreOptions {
|
|
5
|
+
/**
|
|
6
|
+
* Override the shared claim-ledger directory. Defaults to `<cwd>/.wrongstack`
|
|
7
|
+
* (git-ignored, shared by every session on the same working tree). Tests
|
|
8
|
+
* inject a temp dir; two EventBuses sharing one dir dedupe like two hosts.
|
|
9
|
+
*/
|
|
10
|
+
storeDir?: string | undefined;
|
|
11
|
+
/** Claim lifetime in ms before it is treated as stale (default 30 min). */
|
|
12
|
+
ttlMs?: number | undefined;
|
|
13
|
+
/** Ledger compaction threshold in lines (default 10,000). Test override. */
|
|
14
|
+
maxLedgerLines?: number | undefined;
|
|
15
|
+
}
|
|
16
|
+
export declare function breakStaleLock(lockPath: string): Promise<boolean>;
|
|
4
17
|
/**
|
|
5
18
|
* Record review events emitted outside the automatic plugin paths, such as
|
|
6
19
|
* `/review`, so the post-session pass also avoids repeating those files.
|
|
7
20
|
*/
|
|
8
|
-
export declare function recordStartedReview(events: EventBus, payload: unknown): void
|
|
21
|
+
export declare function recordStartedReview(events: EventBus, payload: unknown, opts?: ClaimStoreOptions | undefined): Promise<void>;
|
|
9
22
|
/**
|
|
10
23
|
* Release the exact claims associated with a completed review bundle. Bundle
|
|
11
24
|
* identity prevents an older completion from removing newer in-flight claims.
|
|
12
25
|
*/
|
|
13
|
-
export declare function recordCompletedReview(events: EventBus, payload: unknown): void
|
|
26
|
+
export declare function recordCompletedReview(events: EventBus, payload: unknown): Promise<void>;
|
|
14
27
|
/**
|
|
15
28
|
* Atomically claim review inputs and emit only files whose exact content does
|
|
16
|
-
* not already have a review in progress
|
|
17
|
-
* claimed files and may be a
|
|
29
|
+
* not already have a review in progress — across every session sharing the
|
|
30
|
+
* working tree. The returned bundle contains only claimed files and may be a
|
|
31
|
+
* strict subset of the input.
|
|
18
32
|
*
|
|
19
|
-
* Claims are installed
|
|
20
|
-
* concurrent mid/post-session handlers
|
|
21
|
-
*
|
|
33
|
+
* Claims are installed in the shared ledger (under its exclusive lock) before
|
|
34
|
+
* the event emission, so concurrent mid/post-session handlers — in this
|
|
35
|
+
* process or another — cannot enqueue the same content twice. A synchronous
|
|
36
|
+
* emit failure rolls the claims back.
|
|
22
37
|
*/
|
|
23
|
-
export declare function emitReviewIfChanged(api: Pick<PluginAPI, 'events' | 'emitCustom'>, bundle: ReviewContextBundle): ReviewContextBundle | null
|
|
38
|
+
export declare function emitReviewIfChanged(api: Pick<PluginAPI, 'events' | 'emitCustom'>, bundle: ReviewContextBundle, opts?: ClaimStoreOptions | undefined): Promise<ReviewContextBundle | null>;
|
|
24
39
|
//# sourceMappingURL=review-claim-registry.d.ts.map
|
|
@@ -45,6 +45,8 @@ export interface ReviewContextBundle {
|
|
|
45
45
|
* Absent for ordinary Chimera and manual review triggers.
|
|
46
46
|
*/
|
|
47
47
|
reviewFallbackModels?: string[] | undefined;
|
|
48
|
+
/** Selection policy for the resolved auto-review profile pool. */
|
|
49
|
+
reviewModelSelection?: 'round-robin' | 'random' | undefined;
|
|
48
50
|
/**
|
|
49
51
|
* Cascade severity threshold from the auto-review plugin config.
|
|
50
52
|
* When the review subagent finds findings at or above this level, a
|
package/dist/registry/index.js
CHANGED
|
@@ -269,80 +269,6 @@ function formatContext(ctx) {
|
|
|
269
269
|
return parts.length > 0 ? `[${parts.join(" ")}]` : "";
|
|
270
270
|
}
|
|
271
271
|
|
|
272
|
-
// src/utils/tool-description-mode.ts
|
|
273
|
-
var ORIGINAL_TOOL_DESCRIPTION = /* @__PURE__ */ Symbol.for("wrongstack.tool.originalDescription");
|
|
274
|
-
function normalizeToolDescriptionMode(value) {
|
|
275
|
-
if (typeof value !== "string") return void 0;
|
|
276
|
-
const raw = value.trim().toLowerCase();
|
|
277
|
-
if (raw === "extend" || raw === "extended" || raw === "full") return "extend";
|
|
278
|
-
if (raw === "simple" || raw === "short" || raw === "brief") return "simple";
|
|
279
|
-
return void 0;
|
|
280
|
-
}
|
|
281
|
-
function simplifyToolDescription(text, opts = {}) {
|
|
282
|
-
const maxSentences = Math.max(1, opts.maxSentences ?? 2);
|
|
283
|
-
const maxChars = Math.max(40, opts.maxChars ?? 180);
|
|
284
|
-
const normalized = text.replace(/\r\n?/g, "\n").split("\n").map((line) => line.trim()).filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
|
|
285
|
-
if (normalized.length <= maxChars) return normalized;
|
|
286
|
-
const sentences = normalized.match(/[^.!?]+[.!?]+(?=\s|$)|[^.!?]+$/g) ?? [normalized];
|
|
287
|
-
const selected = [];
|
|
288
|
-
for (const sentence of sentences) {
|
|
289
|
-
selected.push(sentence.trim());
|
|
290
|
-
const candidate = selected.join(" ");
|
|
291
|
-
if (selected.length >= maxSentences || candidate.length >= maxChars) break;
|
|
292
|
-
}
|
|
293
|
-
const summary = selected.join(" ").trim() || normalized;
|
|
294
|
-
if (summary.length <= maxChars) return summary;
|
|
295
|
-
const hardLimit = maxChars - 4;
|
|
296
|
-
const boundary = findWordBoundary(summary, hardLimit);
|
|
297
|
-
return `${summary.slice(0, boundary > 0 ? boundary : hardLimit).trimEnd()} ...`;
|
|
298
|
-
}
|
|
299
|
-
function applyToolDescriptionModeToTool(tool, mode) {
|
|
300
|
-
const existingOriginal = getOriginalDescription(tool);
|
|
301
|
-
if (mode === "extend" && !existingOriginal) return tool;
|
|
302
|
-
const original = existingOriginal ?? {
|
|
303
|
-
description: tool.description,
|
|
304
|
-
usageHint: tool.usageHint
|
|
305
|
-
};
|
|
306
|
-
const next = mode === "simple" ? withDescription(tool, {
|
|
307
|
-
description: simplifyToolDescription(original.description),
|
|
308
|
-
usageHint: original.usageHint === void 0 ? void 0 : simplifyToolDescription(original.usageHint)
|
|
309
|
-
}) : withDescription(tool, original);
|
|
310
|
-
return attachOriginalDescription(next, original);
|
|
311
|
-
}
|
|
312
|
-
function getOriginalDescription(tool) {
|
|
313
|
-
return tool[ORIGINAL_TOOL_DESCRIPTION];
|
|
314
|
-
}
|
|
315
|
-
function attachOriginalDescription(tool, original) {
|
|
316
|
-
Object.defineProperty(tool, ORIGINAL_TOOL_DESCRIPTION, {
|
|
317
|
-
configurable: true,
|
|
318
|
-
enumerable: false,
|
|
319
|
-
value: original,
|
|
320
|
-
writable: true
|
|
321
|
-
});
|
|
322
|
-
return tool;
|
|
323
|
-
}
|
|
324
|
-
function withDescription(tool, next) {
|
|
325
|
-
const copy = {
|
|
326
|
-
...tool,
|
|
327
|
-
description: next.description,
|
|
328
|
-
usageHint: next.usageHint
|
|
329
|
-
};
|
|
330
|
-
if (next.usageHint === void 0) {
|
|
331
|
-
delete copy.usageHint;
|
|
332
|
-
}
|
|
333
|
-
return copy;
|
|
334
|
-
}
|
|
335
|
-
function findWordBoundary(text, limit) {
|
|
336
|
-
const semantic = Math.max(
|
|
337
|
-
text.lastIndexOf(". ", limit),
|
|
338
|
-
text.lastIndexOf("; ", limit),
|
|
339
|
-
text.lastIndexOf(", ", limit)
|
|
340
|
-
);
|
|
341
|
-
if (semantic > 40) return semantic + 1;
|
|
342
|
-
const space = text.lastIndexOf(" ", limit);
|
|
343
|
-
return space > 40 ? space : limit;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
272
|
// src/utils/tool-wire-compact.ts
|
|
347
273
|
var TOOL_DESCRIPTION_MAX_CHARS = 400;
|
|
348
274
|
var SCHEMA_DESCRIPTION_MAX_CHARS = 120;
|
|
@@ -462,6 +388,80 @@ function estimateToolDefTokens(tool) {
|
|
|
462
388
|
return RoughTokenEstimate(tool.name) + RoughTokenEstimate(compact.description) + RoughTokenEstimate(JSON.stringify(compact.inputSchema));
|
|
463
389
|
}
|
|
464
390
|
|
|
391
|
+
// src/utils/tool-description-mode.ts
|
|
392
|
+
var ORIGINAL_TOOL_DESCRIPTION = /* @__PURE__ */ Symbol.for("wrongstack.tool.originalDescription");
|
|
393
|
+
function normalizeToolDescriptionMode(value) {
|
|
394
|
+
if (typeof value !== "string") return void 0;
|
|
395
|
+
const raw = value.trim().toLowerCase();
|
|
396
|
+
if (raw === "extend" || raw === "extended" || raw === "full") return "extend";
|
|
397
|
+
if (raw === "simple" || raw === "short" || raw === "brief") return "simple";
|
|
398
|
+
return void 0;
|
|
399
|
+
}
|
|
400
|
+
function simplifyToolDescription(text, opts = {}) {
|
|
401
|
+
const maxSentences = Math.max(1, opts.maxSentences ?? 2);
|
|
402
|
+
const maxChars = Math.max(40, opts.maxChars ?? 180);
|
|
403
|
+
const normalized = text.replace(/\r\n?/g, "\n").split("\n").map((line) => line.trim()).filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
|
|
404
|
+
if (normalized.length <= maxChars) return normalized;
|
|
405
|
+
const sentences = normalized.match(/[^.!?]+[.!?]+(?=\s|$)|[^.!?]+$/g) ?? [normalized];
|
|
406
|
+
const selected = [];
|
|
407
|
+
for (const sentence of sentences) {
|
|
408
|
+
selected.push(sentence.trim());
|
|
409
|
+
const candidate = selected.join(" ");
|
|
410
|
+
if (selected.length >= maxSentences || candidate.length >= maxChars) break;
|
|
411
|
+
}
|
|
412
|
+
const summary = selected.join(" ").trim() || normalized;
|
|
413
|
+
if (summary.length <= maxChars) return summary;
|
|
414
|
+
const hardLimit = maxChars - 4;
|
|
415
|
+
const boundary = findWordBoundary(summary, hardLimit);
|
|
416
|
+
return `${summary.slice(0, boundary > 0 ? boundary : hardLimit).trimEnd()} ...`;
|
|
417
|
+
}
|
|
418
|
+
function applyToolDescriptionModeToTool(tool, mode) {
|
|
419
|
+
const existingOriginal = getOriginalDescription(tool);
|
|
420
|
+
if (mode === "extend" && !existingOriginal) return tool;
|
|
421
|
+
const original = existingOriginal ?? {
|
|
422
|
+
description: tool.description,
|
|
423
|
+
usageHint: tool.usageHint
|
|
424
|
+
};
|
|
425
|
+
const next = mode === "simple" ? withDescription(tool, {
|
|
426
|
+
description: simplifyToolDescription(original.description),
|
|
427
|
+
usageHint: original.usageHint === void 0 ? void 0 : simplifyToolDescription(original.usageHint)
|
|
428
|
+
}) : withDescription(tool, original);
|
|
429
|
+
return attachOriginalDescription(next, original);
|
|
430
|
+
}
|
|
431
|
+
function getOriginalDescription(tool) {
|
|
432
|
+
return tool[ORIGINAL_TOOL_DESCRIPTION];
|
|
433
|
+
}
|
|
434
|
+
function attachOriginalDescription(tool, original) {
|
|
435
|
+
Object.defineProperty(tool, ORIGINAL_TOOL_DESCRIPTION, {
|
|
436
|
+
configurable: true,
|
|
437
|
+
enumerable: false,
|
|
438
|
+
value: original,
|
|
439
|
+
writable: true
|
|
440
|
+
});
|
|
441
|
+
return tool;
|
|
442
|
+
}
|
|
443
|
+
function withDescription(tool, next) {
|
|
444
|
+
const copy = {
|
|
445
|
+
...tool,
|
|
446
|
+
description: next.description,
|
|
447
|
+
usageHint: next.usageHint
|
|
448
|
+
};
|
|
449
|
+
if (next.usageHint === void 0) {
|
|
450
|
+
delete copy.usageHint;
|
|
451
|
+
}
|
|
452
|
+
return copy;
|
|
453
|
+
}
|
|
454
|
+
function findWordBoundary(text, limit) {
|
|
455
|
+
const semantic = Math.max(
|
|
456
|
+
text.lastIndexOf(". ", limit),
|
|
457
|
+
text.lastIndexOf("; ", limit),
|
|
458
|
+
text.lastIndexOf(", ", limit)
|
|
459
|
+
);
|
|
460
|
+
if (semantic > 40) return semantic + 1;
|
|
461
|
+
const space = text.lastIndexOf(" ", limit);
|
|
462
|
+
return space > 40 ? space : limit;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
465
|
// src/registry/tool-registry.ts
|
|
466
466
|
var ToolRegistry = class _ToolRegistry {
|
|
467
467
|
tools = /* @__PURE__ */ new Map();
|
|
@@ -477,6 +477,15 @@ var ToolRegistry = class _ToolRegistry {
|
|
|
477
477
|
/** Cached `list()` result, frozen after build. Invalidated on _version change. */
|
|
478
478
|
_listSnapshot;
|
|
479
479
|
_listSnapshotVersion = -1;
|
|
480
|
+
/**
|
|
481
|
+
* Optional direct provider surface. `undefined` preserves the legacy
|
|
482
|
+
* behaviour (every enabled catalog tool is direct); when set, `list()` still
|
|
483
|
+
* exposes the complete executable catalog while `listForProvider()` returns
|
|
484
|
+
* only these names. This keeps lazy tools discoverable by tool_search/tool_use.
|
|
485
|
+
*/
|
|
486
|
+
_providerToolNames;
|
|
487
|
+
_providerListSnapshot;
|
|
488
|
+
_providerListSnapshotVersion = -1;
|
|
480
489
|
/** Pre-compute tool definition token estimate once at registration time. */
|
|
481
490
|
_stampDefTokens(tool) {
|
|
482
491
|
if (tool._estDefTokens === void 0) {
|
|
@@ -726,6 +735,30 @@ var ToolRegistry = class _ToolRegistry {
|
|
|
726
735
|
this._listSnapshotVersion = this._version;
|
|
727
736
|
return arr;
|
|
728
737
|
}
|
|
738
|
+
/** Replace the direct provider surface. Pass `undefined` to expose the catalog. */
|
|
739
|
+
setProviderToolNames(names) {
|
|
740
|
+
this._providerToolNames = names ? new Set(names) : void 0;
|
|
741
|
+
this._version++;
|
|
742
|
+
}
|
|
743
|
+
/** Add registered or future tool names to the direct provider surface. */
|
|
744
|
+
exposeToProvider(names) {
|
|
745
|
+
if (!this._providerToolNames) return;
|
|
746
|
+
for (const name of typeof names === "string" ? [names] : names) {
|
|
747
|
+
this._providerToolNames.add(name);
|
|
748
|
+
}
|
|
749
|
+
this._version++;
|
|
750
|
+
}
|
|
751
|
+
/** Tools serialized into provider requests and described by the system prompt. */
|
|
752
|
+
listForProvider() {
|
|
753
|
+
if (!this._providerToolNames) return this.list();
|
|
754
|
+
if (this._providerListSnapshot && this._version === this._providerListSnapshotVersion) {
|
|
755
|
+
return this._providerListSnapshot;
|
|
756
|
+
}
|
|
757
|
+
const arr = Array.from(this.tools.entries()).filter(([name]) => !this._disabled.has(name) && this._providerToolNames?.has(name) === true).map(([, entry]) => entry.tool);
|
|
758
|
+
this._providerListSnapshot = arr;
|
|
759
|
+
this._providerListSnapshotVersion = this._version;
|
|
760
|
+
return arr;
|
|
761
|
+
}
|
|
729
762
|
/**
|
|
730
763
|
* Group tools by their `category` field. Tools without a category
|
|
731
764
|
* are placed under the key `""` (empty string). Returns a Map of
|
|
@@ -752,6 +785,7 @@ var ToolRegistry = class _ToolRegistry {
|
|
|
752
785
|
this.tools.clear();
|
|
753
786
|
this.descriptionModes.clear();
|
|
754
787
|
this._disabled.clear();
|
|
788
|
+
this._providerToolNames = void 0;
|
|
755
789
|
this._version++;
|
|
756
790
|
}
|
|
757
791
|
/**
|
|
@@ -770,6 +804,7 @@ var ToolRegistry = class _ToolRegistry {
|
|
|
770
804
|
for (const name of this._disabled) {
|
|
771
805
|
copy._disabled.add(name);
|
|
772
806
|
}
|
|
807
|
+
copy._providerToolNames = this._providerToolNames ? new Set(this._providerToolNames) : void 0;
|
|
773
808
|
copy._version = this._version;
|
|
774
809
|
return copy;
|
|
775
810
|
}
|
|
@@ -36,6 +36,15 @@ export declare class ToolRegistry {
|
|
|
36
36
|
/** Cached `list()` result, frozen after build. Invalidated on _version change. */
|
|
37
37
|
private _listSnapshot;
|
|
38
38
|
private _listSnapshotVersion;
|
|
39
|
+
/**
|
|
40
|
+
* Optional direct provider surface. `undefined` preserves the legacy
|
|
41
|
+
* behaviour (every enabled catalog tool is direct); when set, `list()` still
|
|
42
|
+
* exposes the complete executable catalog while `listForProvider()` returns
|
|
43
|
+
* only these names. This keeps lazy tools discoverable by tool_search/tool_use.
|
|
44
|
+
*/
|
|
45
|
+
private _providerToolNames;
|
|
46
|
+
private _providerListSnapshot;
|
|
47
|
+
private _providerListSnapshotVersion;
|
|
39
48
|
/** Pre-compute tool definition token estimate once at registration time. */
|
|
40
49
|
private _stampDefTokens;
|
|
41
50
|
/** Apply the description mode transform and stamp token estimates. */
|
|
@@ -134,6 +143,12 @@ export declare class ToolRegistry {
|
|
|
134
143
|
owner: string;
|
|
135
144
|
}[];
|
|
136
145
|
list(): Tool[];
|
|
146
|
+
/** Replace the direct provider surface. Pass `undefined` to expose the catalog. */
|
|
147
|
+
setProviderToolNames(names: readonly string[] | undefined): void;
|
|
148
|
+
/** Add registered or future tool names to the direct provider surface. */
|
|
149
|
+
exposeToProvider(names: string | readonly string[]): void;
|
|
150
|
+
/** Tools serialized into provider requests and described by the system prompt. */
|
|
151
|
+
listForProvider(): Tool[];
|
|
137
152
|
/**
|
|
138
153
|
* Group tools by their `category` field. Tools without a category
|
|
139
154
|
* are placed under the key `""` (empty string). Returns a Map of
|
|
@@ -45,6 +45,8 @@ export declare const ToolCapabilities: {
|
|
|
45
45
|
readonly NET_OUTBOUND: 'net.outbound';
|
|
46
46
|
/** Can mutate in-memory session todos only. */
|
|
47
47
|
readonly SESSION_TODO: 'session.todo';
|
|
48
|
+
/** Can park after-task `<nextsteps>` suggestions for the current turn. */
|
|
49
|
+
readonly SESSION_NEXTSTEPS: 'session.nextsteps';
|
|
48
50
|
/** Can mutate in-memory session mode only. */
|
|
49
51
|
readonly SESSION_MODE: 'session.mode';
|
|
50
52
|
/** Can inspect registered tool metadata. */
|
package/dist/security/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export { restrictDirPermissions, restrictFilePermissions, SECRET_DIR_MODE, SECRE
|
|
|
8
8
|
export { evaluateToolKanbanBoundary, type ToolKanbanBoundaryEvaluation, } from './kanban-boundary.js';
|
|
9
9
|
export { AutoApprovePermissionPolicy, alwaysAllowUnavailableReason, DefaultPermissionPolicy, type PermissionPolicyOptions, } from './permission-policy.js';
|
|
10
10
|
export { TRUST_POLICY_JSON_SCHEMA, TRUST_POLICY_LIMITS, TRUST_POLICY_SCHEMA_VERSION, type TrustPolicyDiagnostic, type TrustPolicyDiagnosticCode, type TrustPolicyValidationResult, validateTrustPolicy, } from './permission-policy-schema.js';
|
|
11
|
-
export { ReadOnlyPermissionPolicy } from './readonly-permission-policy.js';
|
|
11
|
+
export { ReadOnlyPermissionPolicy, toolMutates } from './readonly-permission-policy.js';
|
|
12
12
|
export { DefaultSecretScrubber } from './secret-scrubber.js';
|
|
13
13
|
export { base32Decode, base32Encode, buildOtpAuthUri, generateRecoveryCodes, generateTotpSecret, generateTotp, hashRecoveryCode, verifyRecoveryCode, verifyTotp, verifyTotpCounter, } from './totp.js';
|
|
14
14
|
export { DefaultSecretVault, migratePlaintextSecrets, rewriteConfigEncrypted, rotateConfigKeys, type SecretVaultOptions, } from './secret-vault.js';
|
package/dist/security/index.js
CHANGED
|
@@ -47,6 +47,8 @@ var ToolCapabilities = {
|
|
|
47
47
|
NET_OUTBOUND: "net.outbound",
|
|
48
48
|
/** Can mutate in-memory session todos only. */
|
|
49
49
|
SESSION_TODO: "session.todo",
|
|
50
|
+
/** Can park after-task `<nextsteps>` suggestions for the current turn. */
|
|
51
|
+
SESSION_NEXTSTEPS: "session.nextsteps",
|
|
50
52
|
/** Can mutate in-memory session mode only. */
|
|
51
53
|
SESSION_MODE: "session.mode",
|
|
52
54
|
/** Can inspect registered tool metadata. */
|
|
@@ -3166,9 +3168,11 @@ var MUTATION_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
|
3166
3168
|
ToolCapabilities.PACKAGE_INSTALL,
|
|
3167
3169
|
ToolCapabilities.SUBAGENT_SPAWN
|
|
3168
3170
|
]);
|
|
3169
|
-
function
|
|
3171
|
+
function toolMutates(tool) {
|
|
3172
|
+
if (tool.mutating === true) return true;
|
|
3170
3173
|
return (tool.capabilities ?? []).some((c) => MUTATION_CAPABILITIES.has(c));
|
|
3171
3174
|
}
|
|
3175
|
+
var hasMutationCapability = toolMutates;
|
|
3172
3176
|
function isAllowedReportFile(input, projectRoot) {
|
|
3173
3177
|
if (!input || typeof input !== "object") return false;
|
|
3174
3178
|
const record = input;
|
|
@@ -3201,10 +3205,12 @@ var ReadOnlyPermissionPolicy = class {
|
|
|
3201
3205
|
if (isAllowedReportFile(input, this.projectRoot)) {
|
|
3202
3206
|
return this.inner.evaluate(tool, input, ctx);
|
|
3203
3207
|
}
|
|
3208
|
+
const matched = (tool.capabilities ?? []).filter((c) => MUTATION_CAPABILITIES.has(c));
|
|
3209
|
+
const why = matched.length > 0 ? `requires mutation capabilities (${matched.join(", ")})` : "is declared mutating";
|
|
3204
3210
|
return {
|
|
3205
3211
|
permission: "deny",
|
|
3206
3212
|
source: "readonly_mode",
|
|
3207
|
-
reason: `Session is in read-only mode: tool "${tool.name}"
|
|
3213
|
+
reason: `Session is in read-only mode: tool "${tool.name}" ${why}, which is not allowed. Only .md report files under .temp_files/ may be written.`
|
|
3208
3214
|
};
|
|
3209
3215
|
}
|
|
3210
3216
|
async trust(rule) {
|
|
@@ -4227,6 +4233,7 @@ export {
|
|
|
4227
4233
|
sanitizeApiError,
|
|
4228
4234
|
scrubErrorDetail,
|
|
4229
4235
|
scrubErrorText,
|
|
4236
|
+
toolMutates,
|
|
4230
4237
|
validateDirectoryPolicy,
|
|
4231
4238
|
validateTrustPolicy,
|
|
4232
4239
|
verifyRecoveryCode,
|
|
@@ -31,6 +31,26 @@
|
|
|
31
31
|
import type { Context } from '../core/context.js';
|
|
32
32
|
import type { PermissionDecision, PermissionPolicy, PermissionTrace } from '../types/permission.js';
|
|
33
33
|
import type { Tool } from '../types/tool.js';
|
|
34
|
+
/**
|
|
35
|
+
* Does this tool change something?
|
|
36
|
+
*
|
|
37
|
+
* `capabilities` is the precise answer and stays authoritative — but it is
|
|
38
|
+
* OPT-IN metadata, and a repo-wide count found 154 tools declaring
|
|
39
|
+
* `mutating: true` of which 121 carried no mutation capability at all (28 of
|
|
40
|
+
* those at `permission: 'auto'`, including tools that write API keys and one
|
|
41
|
+
* that sends messages outbound). Read-only mode consulted only `capabilities`,
|
|
42
|
+
* so every one of them ran unblocked and unprompted, while
|
|
43
|
+
* `permission-policy.ts` was already answering the same question with
|
|
44
|
+
* `tool.mutating || …`.
|
|
45
|
+
*
|
|
46
|
+
* Trusting `mutating` here closes all 121 at once and makes the two policies
|
|
47
|
+
* agree. It fails in the safe direction: a tool that declares itself mutating
|
|
48
|
+
* is blocked in read-only mode even if its capability list is incomplete.
|
|
49
|
+
* Fixing the capability labels is still worth doing — it is what the finer
|
|
50
|
+
* grained gates key on — but it is no longer what stands between read-only
|
|
51
|
+
* mode and a write.
|
|
52
|
+
*/
|
|
53
|
+
export declare function toolMutates(tool: Tool): boolean;
|
|
34
54
|
/**
|
|
35
55
|
* Read-only permission policy — session-scoped toggle via ctx.meta['readOnly'].
|
|
36
56
|
*
|
|
@@ -1,12 +1,39 @@
|
|
|
1
1
|
import type { SessionRegistryEntry } from './session-registry-types.js';
|
|
2
2
|
/** Also the scan interval for {@link pruneStaleTempFiles} — see its caller. */
|
|
3
3
|
export declare const STALE_TMP_MS = 60000;
|
|
4
|
+
/** `host:pid` owner stamp written into the advisory lock on acquisition. */
|
|
5
|
+
export declare function lockOwnerStamp(): string;
|
|
4
6
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Unlink `lockPath` only if it still carries OUR exact host:pid stamp. A
|
|
8
|
+
* stale-lock breaker may have stolen the lock and re-stamped it with another
|
|
9
|
+
* host:pid; the resumed holder must not remove the new owner's lock. An EMPTY
|
|
10
|
+
* read is never ours to remove either — it may be the new owner's lock in its
|
|
11
|
+
* open-to-stamp gap (that lock is mtime-bounded instead).
|
|
12
|
+
*
|
|
13
|
+
* Residual race: between the owner read and the unlink, a foreign-host lease
|
|
14
|
+
* break could replace the lock; unlinking would then remove the new owner's
|
|
15
|
+
* lock. Exploiting it requires a >STALE_LOCK_MS hold AND a rename+stamp
|
|
16
|
+
* landing inside that microsecond window, so it is accepted and bounded by
|
|
17
|
+
* the same foreign-lease logic that enabled it.
|
|
18
|
+
*/
|
|
19
|
+
export declare function maybeUnlinkOwnedLock(lockPath: string): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Break a contended lock if it is stale: a same-host owner pid that is no
|
|
22
|
+
* longer alive, or a foreign-host/ownerless lock older than
|
|
23
|
+
* {@link STALE_LOCK_MS}. A live same-host owner's lock is only broken past
|
|
24
|
+
* {@link SAME_HOST_STALE_MS} — a live-but-wedged holder must not degrade
|
|
25
|
+
* cross-session writes forever, but mtime alone must not false-positive while
|
|
26
|
+
* the holder is mid-critical-section. Returns true when the lock was removed
|
|
27
|
+
* (caller should retry acquisition).
|
|
28
|
+
*
|
|
29
|
+
* Removal is an atomic RENAME to a unique tombstone, never unlink: with
|
|
30
|
+
* unlink, two waiters can both read a stale lock and one's unlink can remove
|
|
31
|
+
* the OTHER's fresh lock, letting both into the critical section (POSIX).
|
|
32
|
+
* Rename wins for exactly one waiter; losers get ENOENT and simply retry.
|
|
33
|
+
* (A rename landing after the winner re-created the lock could still move the
|
|
34
|
+
* winner's fresh lock — a pre-existing microsecond TOCTOU; the caller's
|
|
35
|
+
* stamp-guarded cleanup prevents the resumed holder from unlinking the new
|
|
36
|
+
* owner's lock.)
|
|
10
37
|
*/
|
|
11
38
|
export declare function breakStaleLock(lockPath: string): Promise<boolean>;
|
|
12
39
|
export declare function writeAtomicFile(filePath: string, registry: Record<string, SessionRegistryEntry>): Promise<void>;
|
|
@@ -109,6 +109,21 @@ export interface AgentEntry {
|
|
|
109
109
|
lastActivityAt: string;
|
|
110
110
|
}
|
|
111
111
|
export type SessionLiveStatus = 'active' | 'idle' | 'closing' | 'stale' | 'lost';
|
|
112
|
+
export interface SessionWebUIEndpointHint {
|
|
113
|
+
role: 'standalone' | 'parent-shell' | 'session-child';
|
|
114
|
+
surface: 'webui' | 'simpleui' | string;
|
|
115
|
+
host: string;
|
|
116
|
+
httpPort: number;
|
|
117
|
+
url: string;
|
|
118
|
+
/** Redundant owner PID used to cross-check against WebUI instance records. */
|
|
119
|
+
pid: number;
|
|
120
|
+
parentPid?: number | undefined;
|
|
121
|
+
parentShellId?: string | undefined;
|
|
122
|
+
runtimeId?: string | undefined;
|
|
123
|
+
attachable?: boolean | undefined;
|
|
124
|
+
protocolVersion?: number | undefined;
|
|
125
|
+
capabilities?: string[] | undefined;
|
|
126
|
+
}
|
|
112
127
|
export interface SessionRegistryEntry {
|
|
113
128
|
sessionId: string;
|
|
114
129
|
projectSlug: string;
|
|
@@ -132,5 +147,7 @@ export interface SessionRegistryEntry {
|
|
|
132
147
|
/** Count of tracked agents */
|
|
133
148
|
agentCount: number;
|
|
134
149
|
agents: AgentEntry[];
|
|
150
|
+
/** Optional WebUI endpoint hint. Session ownership remains keyed by `sessionId` + `pid`. */
|
|
151
|
+
webuiEndpoint?: SessionWebUIEndpointHint | undefined;
|
|
135
152
|
}
|
|
136
153
|
//# sourceMappingURL=session-registry-types.d.ts.map
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
* @module session-registry
|
|
14
14
|
*/
|
|
15
15
|
import type { AgentEntry, SessionRegistryEntry } from './session-registry-types.js';
|
|
16
|
-
export type { AgentActivityTotals, AgentEntry, AgentLiveStatus, AgentRecentMail, AgentRecentTool, AgentTodoItem, SessionLiveStatus, SessionRegistryEntry, } from './session-registry-types.js';
|
|
16
|
+
export type { AgentActivityTotals, AgentEntry, AgentLiveStatus, AgentRecentMail, AgentRecentTool, AgentTodoItem, SessionLiveStatus, SessionRegistryEntry, SessionWebUIEndpointHint, } from './session-registry-types.js';
|
|
17
17
|
export declare class SessionRegistry {
|
|
18
18
|
private readonly filePath;
|
|
19
19
|
private heartbeatTimer;
|
|
@@ -28,6 +28,12 @@ export interface ParsedSkillFrontmatter {
|
|
|
28
28
|
* consult this field, so it neither grants nor restricts any tool.
|
|
29
29
|
*/
|
|
30
30
|
allowedTools?: string[] | undefined;
|
|
31
|
+
/** WrongStack stable runtime capabilities required before this skill may load. */
|
|
32
|
+
requiredCapabilities?: string[] | undefined;
|
|
33
|
+
/** Exact registered tool names required before this skill may render or load. */
|
|
34
|
+
requiredTools?: string[] | undefined;
|
|
35
|
+
/** Capabilities that improve the workflow but have documented fallbacks. */
|
|
36
|
+
optionalCapabilities?: string[] | undefined;
|
|
31
37
|
}
|
|
32
38
|
/**
|
|
33
39
|
* Parse the YAML frontmatter block from a raw SKILL.md file. Returns `{}` when
|
package/dist/skills/index.js
CHANGED
|
@@ -40,7 +40,14 @@ function securityScoreToTier(score) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
// src/skills/frontmatter.ts
|
|
43
|
-
var SCALAR_KEYS = /* @__PURE__ */ new Set([
|
|
43
|
+
var SCALAR_KEYS = /* @__PURE__ */ new Set([
|
|
44
|
+
"name",
|
|
45
|
+
"description",
|
|
46
|
+
"trigger",
|
|
47
|
+
"version",
|
|
48
|
+
"license",
|
|
49
|
+
"compatibility"
|
|
50
|
+
]);
|
|
44
51
|
function parseSkillFrontmatter(raw) {
|
|
45
52
|
const text = normalizeLineEndings(raw);
|
|
46
53
|
if (!text.startsWith("---")) return {};
|
|
@@ -99,8 +106,9 @@ function parseFrontmatterBlock(block) {
|
|
|
99
106
|
out[normalizeKey(key)] = collected.join("\n").trim();
|
|
100
107
|
continue;
|
|
101
108
|
}
|
|
102
|
-
if (key === "allowed-tools" || key === "allowedTools") {
|
|
103
|
-
|
|
109
|
+
if (key === "allowed-tools" || key === "allowedTools" || key === "required-capabilities" || key === "requiredCapabilities" || key === "required-tools" || key === "requiredTools" || key === "optional-capabilities" || key === "optionalCapabilities") {
|
|
110
|
+
const values = rest.replace(/^\[/, "").replace(/\]$/, "").split(/[\s,]+/).map(unquote).filter(Boolean);
|
|
111
|
+
out[normalizeKey(key)] = values;
|
|
104
112
|
i++;
|
|
105
113
|
continue;
|
|
106
114
|
}
|
|
@@ -114,7 +122,11 @@ function parseFrontmatterBlock(block) {
|
|
|
114
122
|
return out;
|
|
115
123
|
}
|
|
116
124
|
function normalizeKey(key) {
|
|
117
|
-
|
|
125
|
+
if (key === "allowed-tools") return "allowedTools";
|
|
126
|
+
if (key === "required-capabilities") return "requiredCapabilities";
|
|
127
|
+
if (key === "required-tools") return "requiredTools";
|
|
128
|
+
if (key === "optional-capabilities") return "optionalCapabilities";
|
|
129
|
+
return key;
|
|
118
130
|
}
|
|
119
131
|
function unquote(s) {
|
|
120
132
|
if (s.length >= 2 && (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'"))) {
|
|
@@ -980,7 +992,12 @@ async function openInEditor(filePath, env = process.env) {
|
|
|
980
992
|
const child = spawn(parts[0], [...parts.slice(1), filePath], {
|
|
981
993
|
stdio: "ignore",
|
|
982
994
|
detached: true,
|
|
983
|
-
shell
|
|
995
|
+
shell,
|
|
996
|
+
// `shell` routes through cmd.exe on win32, which flashes a console window
|
|
997
|
+
// before the editor appears. A GUI editor is unaffected by the flag; the
|
|
998
|
+
// shell wrapper is what it suppresses. Repo convention — see
|
|
999
|
+
// `core/tests/architecture/spawn-convention.test.ts`.
|
|
1000
|
+
windowsHide: true
|
|
984
1001
|
});
|
|
985
1002
|
child.unref();
|
|
986
1003
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract-aware partitioning and sanitization for my.wrongstack.com config sync.
|
|
3
|
+
*
|
|
4
|
+
* The portal's namespace registry owns a fixed set of `config.json` top-level keys and
|
|
5
|
+
* validates each payload against a strict, recursively-specified schema (server repo:
|
|
6
|
+
* `docs/CLIENT_SYNC_CONTRACT.md`). This module is the client half of that contract:
|
|
7
|
+
*
|
|
8
|
+
* - `buildNamespacePayloads` projects the local config onto the contract field tree, so a
|
|
9
|
+
* payload can never carry a field the server does not know (which would 422) — and, more
|
|
10
|
+
* importantly, can never carry credential material: secret-bearing paths simply are not
|
|
11
|
+
* in the tree.
|
|
12
|
+
* - `applyNamespacePayload` merges a pulled server payload back at contract-field
|
|
13
|
+
* granularity, so locally-kept fields the contract does not own (e.g. `tools.exec`,
|
|
14
|
+
* `mcpServers.*.env`, every `providers.*.apiKey`) are never clobbered by a pull.
|
|
15
|
+
*
|
|
16
|
+
* Stripping is therefore structural (projection), with `stripSecretMaterial` as a final
|
|
17
|
+
* defense-in-depth pass mirroring the server's own scan.
|
|
18
|
+
*/
|
|
19
|
+
/** `true` = the whole value is contract-owned; an object = recurse per key; `'*'` = record wildcard. */
|
|
20
|
+
export type ContractNode = true | {
|
|
21
|
+
[key: string]: ContractNode;
|
|
22
|
+
};
|
|
23
|
+
/** Namespace → the contract tree over the `config.json` top-level keys it owns. */
|
|
24
|
+
export declare const CLOUD_SYNC_CONTRACT: Readonly<Record<string, ContractNode>>;
|
|
25
|
+
export declare const CLOUD_SYNC_NAMESPACES: string[];
|
|
26
|
+
/** Server-side schema versions, sent in every push envelope. */
|
|
27
|
+
export declare const NAMESPACE_SCHEMA_VERSIONS: Readonly<Record<string, number>>;
|
|
28
|
+
/**
|
|
29
|
+
* Top-level keys that must never leave the machine, per the contract. Everything not in a
|
|
30
|
+
* contract tree stays local implicitly; this list exists for documentation and tests.
|
|
31
|
+
*/
|
|
32
|
+
export declare const LOCAL_ONLY_TOP_LEVEL: readonly ['version', 'configScope', 'hq', 'cloudSync', 'sync', 'models', 'apiKey', 'baseUrl'];
|
|
33
|
+
/**
|
|
34
|
+
* Defense-in-depth sweep run over every outgoing payload after projection: drops any
|
|
35
|
+
* secret-named key and any string value that is (or contains) an encrypted blob. Returns
|
|
36
|
+
* `undefined` when nothing survives.
|
|
37
|
+
*/
|
|
38
|
+
export declare function stripSecretMaterial(value: unknown): unknown;
|
|
39
|
+
/**
|
|
40
|
+
* Builds the per-namespace payloads to push. Sections absent locally are omitted (every
|
|
41
|
+
* top-level contract key is optional server-side); a namespace with nothing to say is
|
|
42
|
+
* omitted entirely.
|
|
43
|
+
*/
|
|
44
|
+
export declare function buildNamespacePayloads(config: Record<string, unknown>): Record<string, Record<string, unknown>>;
|
|
45
|
+
/**
|
|
46
|
+
* Applies a pulled namespace snapshot to the local config object, returning a new object.
|
|
47
|
+
*
|
|
48
|
+
* Only keys present in the payload are touched (an omitted optional section means "the
|
|
49
|
+
* server doesn't know", never "deleted"), and inside each section only contract-owned
|
|
50
|
+
* paths are replaced — `providers.*.apiKey`, `mcpServers.*.env`, `tools.exec`, and any
|
|
51
|
+
* field newer than the contract stay exactly as they are locally.
|
|
52
|
+
*/
|
|
53
|
+
export declare function applyNamespacePayload(config: Record<string, unknown>, namespace: string, payload: Record<string, unknown>): Record<string, unknown>;
|
|
54
|
+
//# sourceMappingURL=sanitize.d.ts.map
|