@kl-c/matrixos 0.1.40 → 0.1.49
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/budget/agent-budget.d.ts +29 -0
- package/dist/cli/index.js +1 -1
- package/dist/cli-node/index.js +1 -1
- package/dist/index.js +165 -1
- package/dist/observability/progress.d.ts +34 -0
- package/dist/recovery/checkpoint.d.ts +42 -0
- package/dist/resilience/retry.d.ts +18 -0
- package/dist/security/injection-guard.d.ts +32 -0
- package/dist/security/validation-guard.d.ts +25 -0
- package/dist/skills/skill-registry.d.ts +33 -0
- package/dist/skills/skill-test-runner.d.ts +41 -0
- package/package.json +1 -1
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-agent token budget (cost guardrails).
|
|
3
|
+
*
|
|
4
|
+
* Tracks cumulative tokens consumed per agent and triggers an action when
|
|
5
|
+
* the configured budget is exceeded. Inspired by Sprint B9 (cost-alerter)
|
|
6
|
+
* but per-agent instead of global.
|
|
7
|
+
*
|
|
8
|
+
* Defaults: tank=50k, morpheus=200k, general=100k, unlimited for others.
|
|
9
|
+
* Action on overrun: warn (log) by default; can be set to "throttle" (block
|
|
10
|
+
* the call) or "throw" (reject the call) via the action field.
|
|
11
|
+
*/
|
|
12
|
+
export type BudgetAction = "warn" | "throttle" | "throw";
|
|
13
|
+
export type BudgetConfig = {
|
|
14
|
+
tokens: number;
|
|
15
|
+
action: BudgetAction;
|
|
16
|
+
};
|
|
17
|
+
export declare function setBudget(agent: string, config: BudgetConfig): void;
|
|
18
|
+
export declare function recordTokens(agent: string, tokens: number): BudgetStatus;
|
|
19
|
+
export declare function getUsed(agent: string): number;
|
|
20
|
+
export declare function resetBudget(agent?: string): void;
|
|
21
|
+
export type BudgetStatus = {
|
|
22
|
+
used: number;
|
|
23
|
+
limit: number;
|
|
24
|
+
percent: number;
|
|
25
|
+
action: BudgetAction | null;
|
|
26
|
+
over: boolean;
|
|
27
|
+
};
|
|
28
|
+
/** Convenience: throw if action=throw and budget is over. Returns the status either way. */
|
|
29
|
+
export declare function enforceBudget(agent: string, tokens: number): BudgetStatus;
|
package/dist/cli/index.js
CHANGED
|
@@ -2145,7 +2145,7 @@ var package_default;
|
|
|
2145
2145
|
var init_package = __esm(() => {
|
|
2146
2146
|
package_default = {
|
|
2147
2147
|
name: "@kl-c/matrixos",
|
|
2148
|
-
version: "0.1.
|
|
2148
|
+
version: "0.1.49",
|
|
2149
2149
|
description: "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
|
|
2150
2150
|
main: "./dist/index.js",
|
|
2151
2151
|
types: "dist/index.d.ts",
|
package/dist/cli-node/index.js
CHANGED
|
@@ -2145,7 +2145,7 @@ var package_default;
|
|
|
2145
2145
|
var init_package = __esm(() => {
|
|
2146
2146
|
package_default = {
|
|
2147
2147
|
name: "@kl-c/matrixos",
|
|
2148
|
-
version: "0.1.
|
|
2148
|
+
version: "0.1.49",
|
|
2149
2149
|
description: "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
|
|
2150
2150
|
main: "./dist/index.js",
|
|
2151
2151
|
types: "dist/index.d.ts",
|
package/dist/index.js
CHANGED
|
@@ -368023,7 +368023,7 @@ function getCachedVersion(options = {}) {
|
|
|
368023
368023
|
// package.json
|
|
368024
368024
|
var package_default = {
|
|
368025
368025
|
name: "@kl-c/matrixos",
|
|
368026
|
-
version: "0.1.
|
|
368026
|
+
version: "0.1.49",
|
|
368027
368027
|
description: "MaTrixOS \u2014 Agentic OS for OpenCode. Personalizable, communicating, self-improving, resilient.",
|
|
368028
368028
|
main: "./dist/index.js",
|
|
368029
368029
|
types: "dist/index.d.ts",
|
|
@@ -441224,6 +441224,149 @@ async function resolveSessionAgent(client5, sessionId) {
|
|
|
441224
441224
|
// packages/omo-opencode/src/plugin/tool-execute-before.ts
|
|
441225
441225
|
init_constants3();
|
|
441226
441226
|
init_storage();
|
|
441227
|
+
|
|
441228
|
+
// packages/omo-opencode/src/security/validation-guard.ts
|
|
441229
|
+
var PATTERNS = [
|
|
441230
|
+
{
|
|
441231
|
+
id: "bash.rm-recursive",
|
|
441232
|
+
description: "Recursively deletes files (rm -rf). Confirm before running.",
|
|
441233
|
+
match: (c) => c.tool.toLowerCase() === "bash" && typeof c.args.command === "string" && /\brm\s+(-[a-zA-Z]*r[a-zA-Z]*f|-[a-zA-Z]*f[a-zA-Z]*r)\b/.test(c.args.command)
|
|
441234
|
+
},
|
|
441235
|
+
{
|
|
441236
|
+
id: "bash.git-force-push",
|
|
441237
|
+
description: "Force-pushes git history. Can destroy remote work.",
|
|
441238
|
+
match: (c) => c.tool.toLowerCase() === "bash" && typeof c.args.command === "string" && /\bgit\s+push\s+(-[a-zA-Z]*f|--force(?!-with-lease))/.test(c.args.command)
|
|
441239
|
+
},
|
|
441240
|
+
{
|
|
441241
|
+
id: "bash.git-reset-hard",
|
|
441242
|
+
description: "git reset --hard destroys uncommitted changes.",
|
|
441243
|
+
match: (c) => c.tool.toLowerCase() === "bash" && typeof c.args.command === "string" && /\bgit\s+reset\s+--hard\b/.test(c.args.command)
|
|
441244
|
+
},
|
|
441245
|
+
{
|
|
441246
|
+
id: "bash.npm-publish",
|
|
441247
|
+
description: "Publishes a package to a public registry. Confirm version + scope.",
|
|
441248
|
+
match: (c) => c.tool.toLowerCase() === "bash" && typeof c.args.command === "string" && /\bnpm\s+publish\b/.test(c.args.command)
|
|
441249
|
+
},
|
|
441250
|
+
{
|
|
441251
|
+
id: "bash.chmod-777",
|
|
441252
|
+
description: "chmod 777 opens the file to everyone. Confirm intent.",
|
|
441253
|
+
match: (c) => c.tool.toLowerCase() === "bash" && typeof c.args.command === "string" && /\bchmod\s+(-[a-zA-Z]*R\s+)?777\b/.test(c.args.command)
|
|
441254
|
+
},
|
|
441255
|
+
{
|
|
441256
|
+
id: "bash.curl-pipe-bash",
|
|
441257
|
+
description: "Pipes a remote script into bash (remote code execution).",
|
|
441258
|
+
match: (c) => c.tool.toLowerCase() === "bash" && typeof c.args.command === "string" && /curl[^|]*\|\s*(sudo\s+)?bash/.test(c.args.command)
|
|
441259
|
+
},
|
|
441260
|
+
{
|
|
441261
|
+
id: "write.env-or-secrets",
|
|
441262
|
+
description: "Writes to a .env / credentials / secrets file.",
|
|
441263
|
+
match: (c) => {
|
|
441264
|
+
if (c.tool.toLowerCase() !== "write" && c.tool.toLowerCase() !== "edit")
|
|
441265
|
+
return false;
|
|
441266
|
+
const path27 = typeof c.args.filePath === "string" ? c.args.filePath : typeof c.args.path === "string" ? c.args.path : "";
|
|
441267
|
+
return /\.env(\.|$)|\/\.klc-secrets\/|\/\.aws\/credentials|id_rsa|id_ed25519/.test(path27);
|
|
441268
|
+
}
|
|
441269
|
+
},
|
|
441270
|
+
{
|
|
441271
|
+
id: "write.system-path",
|
|
441272
|
+
description: "Writes to a system path (/etc/, /usr/, /var/). Confirm intent.",
|
|
441273
|
+
match: (c) => {
|
|
441274
|
+
if (c.tool.toLowerCase() !== "write" && c.tool.toLowerCase() !== "edit")
|
|
441275
|
+
return false;
|
|
441276
|
+
const path27 = typeof c.args.filePath === "string" ? c.args.filePath : typeof c.args.path === "string" ? c.args.path : "";
|
|
441277
|
+
return /^\s*(\/etc\/|\/usr\/|\/var\/)/.test(path27);
|
|
441278
|
+
}
|
|
441279
|
+
}
|
|
441280
|
+
];
|
|
441281
|
+
function checkValidation(tool3, args) {
|
|
441282
|
+
const call = { tool: tool3, args };
|
|
441283
|
+
for (const p of PATTERNS) {
|
|
441284
|
+
if (p.match(call)) {
|
|
441285
|
+
return {
|
|
441286
|
+
verdict: "deny",
|
|
441287
|
+
reason: `[P-001 Prepare-Never-Submit] Blocked: ${p.description} (pattern: ${p.id}). Ask the user to confirm before running this command.`,
|
|
441288
|
+
pattern: p.id
|
|
441289
|
+
};
|
|
441290
|
+
}
|
|
441291
|
+
}
|
|
441292
|
+
return { verdict: "allow" };
|
|
441293
|
+
}
|
|
441294
|
+
|
|
441295
|
+
// packages/omo-opencode/src/security/injection-guard.ts
|
|
441296
|
+
var INJECTION_PATTERNS = [
|
|
441297
|
+
{ id: "ignore-previous", re: /\bignore\s+(all\s+)?(previous|prior|above)\s+(instructions?|directives?|prompts?)/i, description: "ignore previous instructions" },
|
|
441298
|
+
{ id: "you-are-now", re: /\byou\s+are\s+now\s+(a|an)\s+/i, description: "you are now a \u2026" },
|
|
441299
|
+
{ id: "system-prompt-tag", re: /<\|im_start\|>\s*system|<\|im_end\|>/i, description: "ChatML system tag (model-tag injection)" },
|
|
441300
|
+
{ id: "assistant-tag-injection", re: /<\|im_start\|>\s*assistant\b/i, description: "ChatML assistant tag in user content" },
|
|
441301
|
+
{ id: "developer-mode", re: /\b(developer|dan|jailbreak|do\s+anything\s+now)\s+mode\b/i, description: "jailbreak mode trigger" },
|
|
441302
|
+
{ id: "disregard-safety", re: /\b(disregard|bypass|ignore)\s+(safety|guardrails?|restrictions?|policies)/i, description: "disregard safety guardrails" },
|
|
441303
|
+
{ id: "execute-following", re: /\bexecute\s+the\s+following\s+(command|code|instructions?)/i, description: "execute the following \u2026" },
|
|
441304
|
+
{ id: "new-instructions", re: /\bnew\s+instructions?:/i, description: "new instructions: header" },
|
|
441305
|
+
{ id: "repeat-after-me", re: /\brepeat\s+after\s+me\b/i, description: "repeat after me" },
|
|
441306
|
+
{ id: "system-colon-override", re: /^\s*system\s*:\s*[\w]/im, description: "system: override (line-start)" }
|
|
441307
|
+
];
|
|
441308
|
+
function containsInjection(text) {
|
|
441309
|
+
for (const p of INJECTION_PATTERNS) {
|
|
441310
|
+
const m = p.re.exec(text);
|
|
441311
|
+
if (m) {
|
|
441312
|
+
const start = Math.max(0, m.index - 20);
|
|
441313
|
+
const end = Math.min(text.length, m.index + m[0].length + 20);
|
|
441314
|
+
return { id: p.id, description: p.description, snippet: text.slice(start, end) };
|
|
441315
|
+
}
|
|
441316
|
+
}
|
|
441317
|
+
return null;
|
|
441318
|
+
}
|
|
441319
|
+
var STRICT_FIELDS = {
|
|
441320
|
+
bash: {
|
|
441321
|
+
command: "string-narrow",
|
|
441322
|
+
timeout: "number"
|
|
441323
|
+
},
|
|
441324
|
+
write: {
|
|
441325
|
+
filePath: "string-strict",
|
|
441326
|
+
content: "string-narrow"
|
|
441327
|
+
},
|
|
441328
|
+
edit: {
|
|
441329
|
+
filePath: "string-strict",
|
|
441330
|
+
oldString: "string-strict",
|
|
441331
|
+
newString: "string-narrow"
|
|
441332
|
+
},
|
|
441333
|
+
read: {
|
|
441334
|
+
filePath: "string-strict"
|
|
441335
|
+
},
|
|
441336
|
+
glob: {
|
|
441337
|
+
pattern: "string-strict"
|
|
441338
|
+
},
|
|
441339
|
+
grep: {
|
|
441340
|
+
pattern: "string-strict",
|
|
441341
|
+
path: "string-strict"
|
|
441342
|
+
}
|
|
441343
|
+
};
|
|
441344
|
+
var PATH_RE = /^[^\x00-\x1f]*$/;
|
|
441345
|
+
var MAX_NARROW = 1e5;
|
|
441346
|
+
function sanitizeToolArgs(tool3, args) {
|
|
441347
|
+
const schema2 = STRICT_FIELDS[tool3.toLowerCase()];
|
|
441348
|
+
if (!schema2)
|
|
441349
|
+
return { ok: true };
|
|
441350
|
+
for (const [field, kind] of Object.entries(schema2)) {
|
|
441351
|
+
const v = args[field];
|
|
441352
|
+
if (v === undefined || v === null)
|
|
441353
|
+
continue;
|
|
441354
|
+
if (typeof v !== "string" && typeof v !== "number" && typeof v !== "boolean") {
|
|
441355
|
+
return { ok: false, reason: `field ${field} has wrong type ${typeof v}`, field };
|
|
441356
|
+
}
|
|
441357
|
+
if (typeof v === "string") {
|
|
441358
|
+
if (kind === "string-strict" && !PATH_RE.test(v)) {
|
|
441359
|
+
return { ok: false, reason: `field ${field} contains control characters or is empty`, field };
|
|
441360
|
+
}
|
|
441361
|
+
if (kind === "string-narrow" && v.length > MAX_NARROW) {
|
|
441362
|
+
return { ok: false, reason: `field ${field} too long (${v.length} > ${MAX_NARROW})`, field };
|
|
441363
|
+
}
|
|
441364
|
+
}
|
|
441365
|
+
}
|
|
441366
|
+
return { ok: true };
|
|
441367
|
+
}
|
|
441368
|
+
|
|
441369
|
+
// packages/omo-opencode/src/plugin/tool-execute-before.ts
|
|
441227
441370
|
var BACKGROUND_WAIT_BLOCK_MESSAGE = [
|
|
441228
441371
|
"Background task wait is already managed by the plugin.",
|
|
441229
441372
|
"End this response now and wait for the <system-reminder> completion notification.",
|
|
@@ -441300,6 +441443,27 @@ function createToolExecuteBeforeHandler3(args) {
|
|
|
441300
441443
|
if (egressDecision?.verdict === "deny") {
|
|
441301
441444
|
throw new Error(`Egress blocked: ${egressDecision.reason}`);
|
|
441302
441445
|
}
|
|
441446
|
+
const validation = checkValidation(input.tool, output.args);
|
|
441447
|
+
if (validation.verdict === "deny") {
|
|
441448
|
+
throw new Error(validation.reason);
|
|
441449
|
+
}
|
|
441450
|
+
const sanitize = sanitizeToolArgs(input.tool, output.args);
|
|
441451
|
+
if (!sanitize.ok) {
|
|
441452
|
+
throw new Error(`[A12 injection-guard] Tool arg rejected: ${sanitize.reason} (field: ${sanitize.field})`);
|
|
441453
|
+
}
|
|
441454
|
+
if (input.tool.toLowerCase() === "bash" && typeof output.args.command === "string") {
|
|
441455
|
+
const inj = containsInjection(output.args.command);
|
|
441456
|
+
if (inj) {
|
|
441457
|
+
log2("[A12 injection-guard] Possible prompt injection in bash command", {
|
|
441458
|
+
sessionID: input.sessionID,
|
|
441459
|
+
callID: input.callID,
|
|
441460
|
+
match: inj
|
|
441461
|
+
});
|
|
441462
|
+
if (output.args.command.includes("MTRX-CANARY-")) {
|
|
441463
|
+
throw new Error(`[A12 injection-guard] Bash command contains canary token \u2014 possible exfiltration attempt. Aborting.`);
|
|
441464
|
+
}
|
|
441465
|
+
}
|
|
441466
|
+
}
|
|
441303
441467
|
await hooks.writeExistingFileGuard?.["tool.execute.before"]?.(input, output);
|
|
441304
441468
|
await hooks.notepadWriteGuard?.["tool.execute.before"]?.(input, output);
|
|
441305
441469
|
await hooks.questionLabelTruncator?.["tool.execute.before"]?.(input, output);
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Progress Streaming (ANNEXE A17).
|
|
3
|
+
*
|
|
4
|
+
* Emits structured progress events for long-running tool calls so the
|
|
5
|
+
* gateway (or CLI) can display real-time status instead of a silent wait.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: today the consumer is the structured log. The Telegram gateway
|
|
8
|
+
* (CDC §7) is not implemented yet — when it is, it can subscribe to
|
|
9
|
+
* these events to edit messages in real time. The schema is stable.
|
|
10
|
+
*/
|
|
11
|
+
export type ProgressEvent = {
|
|
12
|
+
type: "task.progress";
|
|
13
|
+
taskId: string;
|
|
14
|
+
tool: string;
|
|
15
|
+
sessionID: string;
|
|
16
|
+
callID: string;
|
|
17
|
+
step: number;
|
|
18
|
+
totalSteps: number;
|
|
19
|
+
message: string;
|
|
20
|
+
startedAt: number;
|
|
21
|
+
elapsedMs: number;
|
|
22
|
+
};
|
|
23
|
+
type Listener = (e: ProgressEvent) => void;
|
|
24
|
+
/** Subscribe to progress events. Returns an unsubscribe function. */
|
|
25
|
+
export declare function onProgress(listener: Listener): () => void;
|
|
26
|
+
/** Start a task. Returns the taskId. */
|
|
27
|
+
export declare function startTask(tool: string, sessionID: string, callID: string, totalSteps?: number): string;
|
|
28
|
+
/** Advance the current task. */
|
|
29
|
+
export declare function advance(message: string): void;
|
|
30
|
+
/** End the current task. */
|
|
31
|
+
export declare function endTask(message?: string): void;
|
|
32
|
+
/** Run an async operation with task progress tracking. */
|
|
33
|
+
export declare function withProgress<T>(tool: string, sessionID: string, callID: string, op: (advance: (msg: string) => void) => Promise<T>): Promise<T>;
|
|
34
|
+
export {};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mid-Task Checkpoint (ANNEXE A9).
|
|
3
|
+
*
|
|
4
|
+
* Saves the in-flight task state every 30s so a crash (network, OOM, SIGKILL)
|
|
5
|
+
* can resume mid-task instead of restarting from scratch.
|
|
6
|
+
*
|
|
7
|
+
* Difference from snapshot (CDC §18.7): checkpoint is automatic + granular
|
|
8
|
+
* (one file, the in-flight task state), snapshot is manual + coarse
|
|
9
|
+
* (full DB + files).
|
|
10
|
+
*
|
|
11
|
+
* Storage: .matrixos/checkpoints/<taskId>.json (last write wins).
|
|
12
|
+
*/
|
|
13
|
+
export type CheckpointState = {
|
|
14
|
+
taskId: string;
|
|
15
|
+
tool: string;
|
|
16
|
+
sessionID: string;
|
|
17
|
+
startedAt: number;
|
|
18
|
+
updatedAt: number;
|
|
19
|
+
step: number;
|
|
20
|
+
totalSteps: number;
|
|
21
|
+
history: {
|
|
22
|
+
tool: string;
|
|
23
|
+
args: Record<string, unknown>;
|
|
24
|
+
at: number;
|
|
25
|
+
ok: boolean;
|
|
26
|
+
}[];
|
|
27
|
+
context: Record<string, unknown>;
|
|
28
|
+
};
|
|
29
|
+
/** Start checkpointing a new task. Replaces any prior current task. */
|
|
30
|
+
export declare function startCheckpoint(taskId: string, tool: string, sessionID: string, totalSteps?: number, context?: Record<string, unknown>): void;
|
|
31
|
+
/** Record that a tool call happened. */
|
|
32
|
+
export declare function recordStep(tool: string, args: Record<string, unknown>, ok: boolean): void;
|
|
33
|
+
/** Update free-form context (e.g. current goal, partial results). */
|
|
34
|
+
export declare function updateContext(patch: Record<string, unknown>): void;
|
|
35
|
+
/** Force a flush to disk (in addition to the 30s timer). */
|
|
36
|
+
export declare function flush(): Promise<void>;
|
|
37
|
+
/** Stop the current checkpoint (call when the task completes). */
|
|
38
|
+
export declare function stopCheckpoint(): void;
|
|
39
|
+
/** Read a saved checkpoint by taskId, if it exists. */
|
|
40
|
+
export declare function loadCheckpoint(taskId: string, dir?: string): Promise<CheckpointState | null>;
|
|
41
|
+
/** List all checkpoints (for crash-recovery on session start). */
|
|
42
|
+
export declare function listCheckpoints(dir?: string): Promise<string[]>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graceful Degradation / Retry with Backoff (ANNEXE A14).
|
|
3
|
+
*
|
|
4
|
+
* Generic retry wrapper with exponential backoff + jitter.
|
|
5
|
+
* Used by the model resolver to retry on transient failures
|
|
6
|
+
* (rate-limit, network blip, 5xx) before falling back to the
|
|
7
|
+
* next provider in the chain.
|
|
8
|
+
*/
|
|
9
|
+
export type RetryOptions = {
|
|
10
|
+
maxAttempts: number;
|
|
11
|
+
baseDelayMs: number;
|
|
12
|
+
maxDelayMs: number;
|
|
13
|
+
jitterRatio: number;
|
|
14
|
+
shouldRetry: (err: unknown) => boolean;
|
|
15
|
+
};
|
|
16
|
+
export declare function withRetry<T>(fn: () => Promise<T>, opts?: Partial<RetryOptions>): Promise<T>;
|
|
17
|
+
/** Default shouldRetry: retry on transient errors, NOT on validation/programmer errors. */
|
|
18
|
+
export declare function isTransientError(err: unknown): boolean;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt Injection Defense (ANNEXE A12).
|
|
3
|
+
*
|
|
4
|
+
* Three defense layers applied at the tool boundary:
|
|
5
|
+
* 1. containsInjection() — detect known prompt-injection patterns in content
|
|
6
|
+
* the agent is about to consume (file contents, command output, etc.).
|
|
7
|
+
* 2. extractCanary() — pull a canary token from the system prompt and let
|
|
8
|
+
* checkCanaryInOutput() flag any tool output that contains it (a sign
|
|
9
|
+
* that the system prompt was exfiltrated into a tool result).
|
|
10
|
+
* 3. sanitizeToolArgs() — reject free-form text in tool arg fields that
|
|
11
|
+
* should be strictly typed (paths, IDs, enums).
|
|
12
|
+
*/
|
|
13
|
+
export type InjectionMatch = {
|
|
14
|
+
id: string;
|
|
15
|
+
description: string;
|
|
16
|
+
snippet: string;
|
|
17
|
+
};
|
|
18
|
+
/** Returns the first injection match found, or null if clean. */
|
|
19
|
+
export declare function containsInjection(text: string): InjectionMatch | null;
|
|
20
|
+
/** Returns the canary token to embed in the system prompt. */
|
|
21
|
+
export declare function generateCanary(): string;
|
|
22
|
+
/** Returns true if the canary appears in the output (sign of exfiltration). */
|
|
23
|
+
export declare function checkCanaryInOutput(output: string, canary: string): boolean;
|
|
24
|
+
export type SanitizeResult = {
|
|
25
|
+
ok: true;
|
|
26
|
+
} | {
|
|
27
|
+
ok: false;
|
|
28
|
+
reason: string;
|
|
29
|
+
field: string;
|
|
30
|
+
};
|
|
31
|
+
/** Reject tool args that don't match the strict schema (no control chars in paths, etc.). */
|
|
32
|
+
export declare function sanitizeToolArgs(tool: string, args: Record<string, unknown>): SanitizeResult;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prepare-Never-Submit (P-001) — tool-level validation guard.
|
|
3
|
+
*
|
|
4
|
+
* Inspired by ANNEXE_A A3. Detects high-risk tool calls BEFORE execution and
|
|
5
|
+
* throws so the agent sees the error and asks the human to confirm.
|
|
6
|
+
*
|
|
7
|
+
* NOTE: The annex proposes a skill-level frontmatter (requires_validation).
|
|
8
|
+
* The current OpenCode hook system does not expose skill context at the
|
|
9
|
+
* tool.execute.before layer, so this guard is PATTERN-based on tool + args.
|
|
10
|
+
* This is less fine-grained but more robust (catches both skill-driven and
|
|
11
|
+
* ad-hoc risky calls) and applicable today without a hook refactor.
|
|
12
|
+
*/
|
|
13
|
+
export type ToolCall = {
|
|
14
|
+
tool: string;
|
|
15
|
+
args: Record<string, unknown>;
|
|
16
|
+
};
|
|
17
|
+
export type ValidationDecision = {
|
|
18
|
+
verdict: "deny";
|
|
19
|
+
reason: string;
|
|
20
|
+
pattern: string;
|
|
21
|
+
} | {
|
|
22
|
+
verdict: "allow";
|
|
23
|
+
};
|
|
24
|
+
/** Decide whether the tool call needs human validation. Throws nothing — pure decision. */
|
|
25
|
+
export declare function checkValidation(tool: string, args: Record<string, unknown>): ValidationDecision;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill versioning.
|
|
3
|
+
*
|
|
4
|
+
* Convention: <root>/<skill-name>/<semver>/SKILL.md
|
|
5
|
+
* The active version is recorded in <root>/<skill-name>/active (a file
|
|
6
|
+
* whose content is the version string, e.g. "1.0.0").
|
|
7
|
+
*
|
|
8
|
+
* Why: when modifying a skill, the old version is preserved. The active
|
|
9
|
+
* pointer lets the runtime pick the current version without breaking
|
|
10
|
+
* the in-flight calls. Rollback = rewrite the active file.
|
|
11
|
+
*/
|
|
12
|
+
export type SkillVersion = {
|
|
13
|
+
name: string;
|
|
14
|
+
version: string;
|
|
15
|
+
path: string;
|
|
16
|
+
};
|
|
17
|
+
export declare class SkillRegistry {
|
|
18
|
+
private root;
|
|
19
|
+
constructor(root: string);
|
|
20
|
+
/** All versions of a skill, newest first. */
|
|
21
|
+
list(name: string): Promise<SkillVersion[]>;
|
|
22
|
+
/** List all skills that have at least one versioned SKILL.md. */
|
|
23
|
+
listAll(): Promise<{
|
|
24
|
+
name: string;
|
|
25
|
+
versions: SkillVersion[];
|
|
26
|
+
}[]>;
|
|
27
|
+
/** Read the active version (or the latest if no active file). */
|
|
28
|
+
getActive(name: string): Promise<SkillVersion | null>;
|
|
29
|
+
/** Set the active version. */
|
|
30
|
+
setActive(name: string, version: string): Promise<void>;
|
|
31
|
+
/** Add a new version (mkdir + write SKILL.md content). */
|
|
32
|
+
addVersion(name: string, version: string, content: string): Promise<SkillVersion>;
|
|
33
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill test runner.
|
|
3
|
+
*
|
|
4
|
+
* Convention: a skill may have a co-located <name>.test.ts (or
|
|
5
|
+
* __tests__/<name>.test.ts) that exercises the skill instructions. The
|
|
6
|
+
* runner discovers these files, parses the skill frontmatter, and exposes
|
|
7
|
+
* the metadata to the test as a fixture.
|
|
8
|
+
*
|
|
9
|
+
* Usage (CLI):
|
|
10
|
+
* matrixos skill test <name> # run a skill's tests
|
|
11
|
+
* matrixos skill test --all # run all skills' tests
|
|
12
|
+
*
|
|
13
|
+
* Usage (programmatic):
|
|
14
|
+
* import { runSkillTest, parseSkillFrontmatter } from "./skill-test-runner"
|
|
15
|
+
*/
|
|
16
|
+
export type SkillFrontmatter = {
|
|
17
|
+
name: string;
|
|
18
|
+
description: string;
|
|
19
|
+
triggers: string[];
|
|
20
|
+
requiresValidation: boolean;
|
|
21
|
+
raw: Record<string, unknown>;
|
|
22
|
+
};
|
|
23
|
+
export declare function parseSkillFrontmatter(content: string): {
|
|
24
|
+
frontmatter: SkillFrontmatter;
|
|
25
|
+
body: string;
|
|
26
|
+
} | null;
|
|
27
|
+
/** Find the test file for a skill. */
|
|
28
|
+
export declare function findSkillTestFile(skillRoot: string, skillName: string): Promise<string | null>;
|
|
29
|
+
/** Run a single skill test (programmatic). Returns a result object. */
|
|
30
|
+
export declare function runSkillTest(skillRoot: string, skillName: string, testFn: (ctx: {
|
|
31
|
+
skill: SkillFrontmatter;
|
|
32
|
+
body: string;
|
|
33
|
+
}) => void | Promise<void>): Promise<{
|
|
34
|
+
ok: boolean;
|
|
35
|
+
error?: string;
|
|
36
|
+
}>;
|
|
37
|
+
/** Discover all skills with test files. */
|
|
38
|
+
export declare function discoverSkillTests(skillRoot: string): Promise<{
|
|
39
|
+
name: string;
|
|
40
|
+
testFile: string;
|
|
41
|
+
}[]>;
|
package/package.json
CHANGED