@kontextmind/kxm 0.6.0 → 0.7.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/.claude-plugin/marketplace.json +1 -1
- package/.kxm/agents/coordinator.yaml +9 -0
- package/.kxm/agents/critic-arch.yaml +13 -0
- package/.kxm/agents/critic-cli.yaml +13 -0
- package/.kxm/agents/implementer.yaml +13 -0
- package/.kxm/gates.yaml +8 -0
- package/.kxm/producers.yaml +22 -0
- package/.kxm/project.yaml +15 -0
- package/.kxm/roles/writer.yaml +5 -0
- package/.kxm/workflows/default.yaml +47 -0
- package/CHANGELOG.md +39 -7
- package/README.md +1 -0
- package/docs/README.md +2 -0
- package/docs/agent-skills.md +118 -0
- package/docs/architecture.md +1 -1
- package/docs/assignment-runner.md +21 -8
- package/docs/configuration.md +2 -2
- package/docs/kxm-handbook.md +3 -3
- package/docs/operator-pi-packages.md +63 -0
- package/docs/skills/repo-work-delivery.md +102 -0
- package/docs/test-matrix.md +4 -3
- package/docs/troubleshooting.md +19 -0
- package/docs/vnext/validation.md +9 -0
- package/docs/webhook-workflows.md +2 -2
- package/examples/README.md +1 -1
- package/package.json +16 -17
- package/plugins/kxm/.claude-plugin/plugin.json +1 -1
- package/plugins/kxm/README.md +1 -1
- package/plugins/kxm/dist/cli.js +6875 -3672
- package/plugins/kxm/dist/core.js +214 -34
- package/plugins/kxm/dist/extension.js +7721 -85
- package/plugins/kxm/dist/mcp-server.js +75 -21
- package/plugins/kxm/dist/runtime.js +5403 -1014
- package/plugins/kxm/dist/server.js +3008 -2268
- package/plugins/kxm/dist/vnext-runtime-supervisor.js +5751 -631
- package/plugins/kxm/package.json +1 -1
- package/plugins/kxm/skills/SUITE.md +5 -0
- package/plugins/kxm/skills/hints.json +73 -0
- package/plugins/kxm/skills/kxm/SKILL.md +30 -83
- package/plugins/kxm/skills/kxm-context-memory/SKILL.md +69 -0
- package/plugins/kxm/skills/kxm-definitions/SKILL.md +65 -0
- package/plugins/kxm/skills/kxm-harness-auth/SKILL.md +34 -0
- package/plugins/kxm/skills/kxm-harvest/SKILL.md +48 -0
- package/plugins/kxm/skills/kxm-hub-ops/SKILL.md +34 -0
- package/plugins/kxm/skills/kxm-insights/SKILL.md +48 -0
- package/plugins/kxm/skills/kxm-mind/SKILL.md +59 -0
- package/plugins/kxm/skills/kxm-peer/SKILL.md +110 -0
- package/plugins/kxm/skills/kxm-project-setup/SKILL.md +35 -0
- package/plugins/kxm/skills/kxm-projects/SKILL.md +43 -0
- package/plugins/kxm/skills/kxm-protocol/SKILL.md +66 -0
- package/plugins/kxm/skills/kxm-query/SKILL.md +45 -0
- package/plugins/kxm/skills/kxm-routing-improve/SKILL.md +30 -0
- package/plugins/kxm/skills/kxm-runs/SKILL.md +29 -0
- package/plugins/kxm/skills/kxm-setup/SKILL.md +55 -0
- package/plugins/kxm/skills/kxm-skill-lifecycle/SKILL.md +31 -0
- package/plugins/kxm/skills/kxm-tasks/SKILL.md +33 -0
- package/plugins/kxm/skills/kxm-triage/SKILL.md +47 -0
- package/plugins/kxm/skills/kxm-work/SKILL.md +44 -0
- package/plugins/kxm/skills/kxm-workflow/SKILL.md +45 -0
- package/plugins/kxm/src/autocomplete.ts +9 -3
- package/plugins/kxm/src/cli.ts +1077 -11
- package/plugins/kxm/src/commands.ts +150 -8
- package/plugins/kxm/src/config.ts +7 -4
- package/plugins/kxm/src/context-packet.ts +172 -0
- package/plugins/kxm/src/extension.ts +36 -1
- package/plugins/kxm/src/external-effects.ts +356 -7
- package/plugins/kxm/src/hub.ts +2 -4
- package/plugins/kxm/src/improve.ts +72 -0
- package/plugins/kxm/src/mcp-server.ts +1 -1
- package/plugins/kxm/src/model-inventory.ts +127 -0
- package/plugins/kxm/src/policy-draft.d.mts +55 -0
- package/plugins/kxm/src/policy-draft.mjs +565 -0
- package/plugins/kxm/src/price-calc.ts +17 -18
- package/plugins/kxm/src/prices.ts +32 -16
- package/plugins/kxm/src/producers.ts +71 -0
- package/plugins/kxm/src/restricted-yaml.d.mts +31 -0
- package/plugins/kxm/src/restricted-yaml.mjs +145 -0
- package/plugins/kxm/src/role.ts +375 -0
- package/plugins/kxm/src/routing.ts +99 -1
- package/plugins/kxm/src/session-work.ts +9 -2
- package/plugins/kxm/src/studio-layout.ts +660 -17
- package/plugins/kxm/src/suggest.ts +7 -13
- package/plugins/kxm/src/telemetry.ts +82 -0
- package/plugins/kxm/src/tui.ts +140 -0
- package/plugins/kxm/src/vnext-config.ts +15 -110
- package/plugins/kxm/src/vnext-engine.ts +198 -62
- package/plugins/kxm/src/vnext-harness.ts +263 -81
- package/plugins/kxm/src/vnext-oneshot-evidence.ts +85 -0
- package/plugins/kxm/src/vnext-oneshot-process.ts +149 -0
- package/plugins/kxm/src/vnext-oneshot-producer.ts +170 -233
- package/plugins/kxm/src/vnext-runtime-store.ts +35 -1
- package/plugins/kxm/src/vnext-runtime-supervisor.ts +120 -5
- package/plugins/kxm/src/vnext-runtime.ts +14 -0
- package/plugins/kxm/src/workflow-manager.ts +392 -0
- package/plugins/kxm/src/workflow-tui.ts +255 -0
- package/schemas/policy-draft/README.md +17 -0
- package/schemas/policy-draft/model.v2.schema.json +140 -0
- package/schemas/policy-draft/role.v2.schema.json +91 -0
- package/schemas/vnext/role.schema.json +76 -0
- package/schemas/vnext/run-event.schema.json +1 -0
- package/scripts/assignment-run.d.mts +1 -1
- package/scripts/assignment-run.mjs +44 -35
- package/scripts/check-generated.mjs +33 -9
- package/scripts/emit-codex-artifacts.mjs +255 -11
- package/scripts/harness-run.d.mts +12 -4
- package/scripts/harness-run.mjs +65 -17
- package/scripts/kxm-hub.mjs +6 -0
- package/scripts/native-critic.d.mts +5 -0
- package/scripts/native-critic.mjs +60 -0
- package/.kxm/config/README.md +0 -5
- package/.kxm/config/agents.json +0 -43
- package/.kxm/config/env.example +0 -56
- package/.kxm/config/update.example.yaml +0 -9
- package/.kxm/config/workflows/fix.json +0 -160
- package/.kxm/config/workflows/jira-development.json +0 -116
- package/.kxm/config/workflows/provenance-quorum.json +0 -150
- package/.kxm/config/workflows/v04-dogfood.json +0 -72
package/plugins/kxm/dist/core.js
CHANGED
|
@@ -68,10 +68,7 @@ function findModelPrice(catalog, model, provider) {
|
|
|
68
68
|
const normalizedModel = model.trim().toLowerCase();
|
|
69
69
|
const normalizedProvider = provider?.trim().toLowerCase();
|
|
70
70
|
for (const entry of catalog.models) {
|
|
71
|
-
if (normalizedProvider && entry.provider.toLowerCase() !== normalizedProvider)
|
|
72
|
-
const matchesAlias = entry.aliases?.some((a) => a.toLowerCase() === normalizedModel);
|
|
73
|
-
if (!matchesAlias) continue;
|
|
74
|
-
}
|
|
71
|
+
if (normalizedProvider && entry.provider.toLowerCase() !== normalizedProvider) continue;
|
|
75
72
|
if (entry.id.toLowerCase() === normalizedModel || entry.model.toLowerCase() === normalizedModel) {
|
|
76
73
|
return entry;
|
|
77
74
|
}
|
|
@@ -84,20 +81,19 @@ function findModelPrice(catalog, model, provider) {
|
|
|
84
81
|
function calculateModelCost(catalog, params) {
|
|
85
82
|
const row = findModelPrice(catalog, params.model, params.provider);
|
|
86
83
|
if (!row || row.tiers.length === 0) return void 0;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
const
|
|
97
|
-
|
|
98
|
-
const cacheRead = params.cacheReadTokens ?? 0;
|
|
99
|
-
const cacheWrite = params.cacheWriteTokens ?? 0;
|
|
84
|
+
if (catalog.currency !== void 0 && catalog.currency !== "USD") return void 0;
|
|
85
|
+
const validCount = (n) => typeof n === "number" && Number.isSafeInteger(n) && n >= 0;
|
|
86
|
+
const tokensIn = params.tokensIn;
|
|
87
|
+
const tokensOut = params.tokensOut;
|
|
88
|
+
const cacheRead = params.cacheReadTokens;
|
|
89
|
+
const cacheWrite = params.cacheWriteTokens;
|
|
90
|
+
if (!validCount(tokensIn) || !validCount(tokensOut) || !validCount(cacheRead) || !validCount(cacheWrite)) return void 0;
|
|
91
|
+
const context = params.contextTokens;
|
|
92
|
+
if (context != null && !validCount(context)) return void 0;
|
|
93
|
+
const selectedTier = context == null ? row.tiers.length === 1 && row.tiers[0].upToContextTokens == null ? row.tiers[0] : void 0 : row.tiers.find((tier) => tier.upToContextTokens == null || context <= tier.upToContextTokens);
|
|
94
|
+
if (!selectedTier || cacheRead > 0 && selectedTier.cacheReadPerMillion == null || cacheWrite > 0 && selectedTier.cacheWritePerMillion == null) return void 0;
|
|
100
95
|
const cost = tokensIn / 1e6 * selectedTier.inputPerMillion + tokensOut / 1e6 * selectedTier.outputPerMillion + cacheRead / 1e6 * (selectedTier.cacheReadPerMillion ?? 0) + cacheWrite / 1e6 * (selectedTier.cacheWritePerMillion ?? 0);
|
|
96
|
+
if (!Number.isFinite(cost) || cost < 0) return void 0;
|
|
101
97
|
const priceRef = `${catalog.date}#${row.id}`;
|
|
102
98
|
return {
|
|
103
99
|
costUsd: Math.round(cost * 1e6) / 1e6,
|
|
@@ -435,6 +431,63 @@ function computePercentile(sorted, p) {
|
|
|
435
431
|
}
|
|
436
432
|
return sorted[base];
|
|
437
433
|
}
|
|
434
|
+
function computeDecayedWeight(recordedAt, halfLifeDays = 14, now = Date.now()) {
|
|
435
|
+
const ts = typeof recordedAt === "number" ? recordedAt : Date.parse(recordedAt);
|
|
436
|
+
if (!Number.isFinite(ts)) return 1;
|
|
437
|
+
const deltaMs = Math.max(0, now - ts);
|
|
438
|
+
const halfLifeMs = halfLifeDays * 24 * 60 * 60 * 1e3;
|
|
439
|
+
return Math.pow(2, -deltaMs / halfLifeMs);
|
|
440
|
+
}
|
|
441
|
+
function evaluateCircuitBreaker(records, route, config, now = Date.now()) {
|
|
442
|
+
const mode = config?.mode ?? "soft_demotion";
|
|
443
|
+
const failureThreshold = config?.failureThreshold ?? 3;
|
|
444
|
+
const windowSeconds = config?.windowSeconds ?? 3600;
|
|
445
|
+
const penaltyMultiplier = config?.penaltyMultiplier ?? 5;
|
|
446
|
+
const windowMs = windowSeconds * 1e3;
|
|
447
|
+
const matching = records.filter((r) => {
|
|
448
|
+
const isV2 = r.schema === ROUTING_RECORD_V2_SCHEMA;
|
|
449
|
+
const harness = isV2 ? r.harness : r.providerMetadata?.harness;
|
|
450
|
+
const model = isV2 ? r.effectiveModel || r.requestedModel : r.effectiveModel || r.requestedModel;
|
|
451
|
+
if (harness !== route.harness || model !== route.model) return false;
|
|
452
|
+
const recAt = r.recordedAt;
|
|
453
|
+
const ts = typeof recAt === "string" ? Date.parse(recAt) : 0;
|
|
454
|
+
return ts >= now - windowMs;
|
|
455
|
+
});
|
|
456
|
+
matching.sort((a, b) => {
|
|
457
|
+
const ta = typeof a.recordedAt === "string" ? Date.parse(a.recordedAt) : 0;
|
|
458
|
+
const tb = typeof b.recordedAt === "string" ? Date.parse(b.recordedAt) : 0;
|
|
459
|
+
return tb - ta;
|
|
460
|
+
});
|
|
461
|
+
let consecutiveFailures = 0;
|
|
462
|
+
for (const r of matching) {
|
|
463
|
+
const passed = r.verifierOutcome === "passed" || r.finalOutcome === "accepted" || r.finalOutcome === "completed";
|
|
464
|
+
if (passed && (r.retries ?? 0) === 0) {
|
|
465
|
+
break;
|
|
466
|
+
}
|
|
467
|
+
consecutiveFailures++;
|
|
468
|
+
}
|
|
469
|
+
if (consecutiveFailures >= failureThreshold) {
|
|
470
|
+
if (mode === "quarantine") {
|
|
471
|
+
return {
|
|
472
|
+
status: "quarantined",
|
|
473
|
+
consecutiveFailures,
|
|
474
|
+
penaltyMultiplier,
|
|
475
|
+
reason: `Route quarantined after ${consecutiveFailures} consecutive failures within ${windowSeconds}s`
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
return {
|
|
479
|
+
status: "demoted",
|
|
480
|
+
consecutiveFailures,
|
|
481
|
+
penaltyMultiplier,
|
|
482
|
+
reason: `Route demoted with ${penaltyMultiplier}x penalty after ${consecutiveFailures} consecutive failures`
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
return {
|
|
486
|
+
status: "healthy",
|
|
487
|
+
consecutiveFailures,
|
|
488
|
+
penaltyMultiplier: 1
|
|
489
|
+
};
|
|
490
|
+
}
|
|
438
491
|
function generateRoutingReport(records, options = {}) {
|
|
439
492
|
const generatedAt = options.now ? options.now() : (/* @__PURE__ */ new Date()).toISOString();
|
|
440
493
|
if (records.length === 0) {
|
|
@@ -538,7 +591,8 @@ function generateRoutingReport(records, options = {}) {
|
|
|
538
591
|
tokensIn: r.tokensIn ?? null,
|
|
539
592
|
tokensOut: r.tokensOut ?? null,
|
|
540
593
|
cacheReadTokens: r.cacheReadTokens ?? null,
|
|
541
|
-
|
|
594
|
+
cacheWriteTokens: v2?.cacheWriteTokens ?? null,
|
|
595
|
+
contextTokens: v2?.contextTokens ?? null
|
|
542
596
|
});
|
|
543
597
|
if (calc) {
|
|
544
598
|
equivTotal += calc.costUsd;
|
|
@@ -742,7 +796,10 @@ function redactStringList(values, maxItems = 32) {
|
|
|
742
796
|
}
|
|
743
797
|
|
|
744
798
|
// plugins/kxm/src/commands.ts
|
|
745
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
799
|
+
import { createHash as createHash2, randomUUID as randomUUID2, timingSafeEqual } from "node:crypto";
|
|
800
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
|
|
801
|
+
import { homedir } from "node:os";
|
|
802
|
+
import { dirname, join, resolve } from "node:path";
|
|
746
803
|
|
|
747
804
|
// plugins/kxm/src/client.ts
|
|
748
805
|
var HubHttpError = class extends Error {
|
|
@@ -1508,26 +1565,60 @@ function parseAttemptToken(token) {
|
|
|
1508
1565
|
}
|
|
1509
1566
|
return void 0;
|
|
1510
1567
|
}
|
|
1568
|
+
function timingSafeStringCompare(a, b) {
|
|
1569
|
+
if (typeof a !== "string" || typeof b !== "string") return false;
|
|
1570
|
+
const hashA = createHash2("sha256").update(a).digest();
|
|
1571
|
+
const hashB = createHash2("sha256").update(b).digest();
|
|
1572
|
+
return timingSafeEqual(hashA, hashB);
|
|
1573
|
+
}
|
|
1511
1574
|
function mintSessionToken(input) {
|
|
1512
1575
|
const toolPolicy = input?.toolPolicy ?? (input?.allowedTools || input?.deniedTools || input?.preset ? {
|
|
1513
1576
|
...input?.preset ? { preset: input.preset } : {},
|
|
1514
|
-
...input?.allowedTools ? { allow: input.allowedTools } : {},
|
|
1515
|
-
...input?.deniedTools ? { deny: input.deniedTools } : {}
|
|
1577
|
+
...input?.allowedTools ? { allow: input.allowedTools, allowedTools: input.allowedTools } : {},
|
|
1578
|
+
...input?.deniedTools ? { deny: input.deniedTools, deniedTools: input.deniedTools } : {}
|
|
1516
1579
|
} : void 0);
|
|
1580
|
+
const issuedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1581
|
+
const defaultTtlMs = 24 * 60 * 60 * 1e3;
|
|
1582
|
+
const ttlMs = typeof input?.ttlMs === "number" && input.ttlMs > 0 ? input.ttlMs : defaultTtlMs;
|
|
1583
|
+
const expiresAt = input?.expiresAt ?? new Date(Date.now() + ttlMs).toISOString();
|
|
1517
1584
|
const payload = {
|
|
1518
1585
|
schema: "kxm.session-token.v1",
|
|
1519
1586
|
sessionId: input?.sessionId ?? `session-${randomUUID2()}`,
|
|
1520
|
-
issuedAt
|
|
1587
|
+
issuedAt,
|
|
1588
|
+
expiresAt,
|
|
1521
1589
|
...input?.agentName ? { agentName: input.agentName } : {},
|
|
1522
1590
|
...toolPolicy ? { toolPolicy } : {}
|
|
1523
1591
|
};
|
|
1524
1592
|
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
1525
1593
|
}
|
|
1594
|
+
function isSessionTokenExpired(payloadOrToken) {
|
|
1595
|
+
let payload;
|
|
1596
|
+
if (typeof payloadOrToken === "string") {
|
|
1597
|
+
try {
|
|
1598
|
+
const raw = Buffer.from(payloadOrToken.trim(), "base64url").toString("utf8");
|
|
1599
|
+
payload = JSON.parse(raw);
|
|
1600
|
+
} catch {
|
|
1601
|
+
return true;
|
|
1602
|
+
}
|
|
1603
|
+
} else {
|
|
1604
|
+
payload = payloadOrToken;
|
|
1605
|
+
}
|
|
1606
|
+
if (!payload || !payload.expiresAt) return false;
|
|
1607
|
+
const expiryTime = new Date(payload.expiresAt).getTime();
|
|
1608
|
+
if (Number.isNaN(expiryTime)) return true;
|
|
1609
|
+
return Date.now() >= expiryTime;
|
|
1610
|
+
}
|
|
1526
1611
|
function parseSessionToken(token) {
|
|
1527
1612
|
try {
|
|
1528
1613
|
const raw = Buffer.from(token.trim(), "base64url").toString("utf8");
|
|
1529
1614
|
const parsed = JSON.parse(raw);
|
|
1530
1615
|
if (parsed && typeof parsed === "object" && parsed.schema === "kxm.session-token.v1" && typeof parsed.sessionId === "string") {
|
|
1616
|
+
if (parsed.expiresAt) {
|
|
1617
|
+
const expiryTime = new Date(parsed.expiresAt).getTime();
|
|
1618
|
+
if (!Number.isNaN(expiryTime) && Date.now() >= expiryTime) {
|
|
1619
|
+
return void 0;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1531
1622
|
return parsed;
|
|
1532
1623
|
}
|
|
1533
1624
|
} catch {
|
|
@@ -1535,6 +1626,49 @@ function parseSessionToken(token) {
|
|
|
1535
1626
|
}
|
|
1536
1627
|
return void 0;
|
|
1537
1628
|
}
|
|
1629
|
+
function resolveUserConfigDirectory(overrideDir) {
|
|
1630
|
+
if (overrideDir) return resolve(overrideDir);
|
|
1631
|
+
return resolve(process.env.KXM_USER_CONFIG_DIR?.trim() || join(homedir(), ".config", "kxm"));
|
|
1632
|
+
}
|
|
1633
|
+
function sessionTokenPath(userConfigDir) {
|
|
1634
|
+
return join(resolveUserConfigDirectory(userConfigDir), "session.token");
|
|
1635
|
+
}
|
|
1636
|
+
function persistSessionTokenToDisk(token, options) {
|
|
1637
|
+
const filePath = sessionTokenPath(options?.userConfigDir);
|
|
1638
|
+
const dir = dirname(filePath);
|
|
1639
|
+
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
1640
|
+
const mode = options?.mode ?? 384;
|
|
1641
|
+
writeFileSync(filePath, `${token.trim()}
|
|
1642
|
+
`, { encoding: "utf8", mode });
|
|
1643
|
+
try {
|
|
1644
|
+
chmodSync(filePath, mode);
|
|
1645
|
+
} catch {
|
|
1646
|
+
}
|
|
1647
|
+
return filePath;
|
|
1648
|
+
}
|
|
1649
|
+
function readSessionTokenFromDisk(options) {
|
|
1650
|
+
const filePath = sessionTokenPath(options?.userConfigDir);
|
|
1651
|
+
if (!existsSync(filePath)) return void 0;
|
|
1652
|
+
try {
|
|
1653
|
+
const token = readFileSync(filePath, "utf8").trim();
|
|
1654
|
+
if (!token) return void 0;
|
|
1655
|
+
const payload = parseSessionToken(token);
|
|
1656
|
+
if (!payload) return void 0;
|
|
1657
|
+
return { token, payload };
|
|
1658
|
+
} catch {
|
|
1659
|
+
return void 0;
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
function clearSessionTokenFromDisk(options) {
|
|
1663
|
+
const filePath = sessionTokenPath(options?.userConfigDir);
|
|
1664
|
+
if (!existsSync(filePath)) return false;
|
|
1665
|
+
try {
|
|
1666
|
+
unlinkSync(filePath);
|
|
1667
|
+
return true;
|
|
1668
|
+
} catch {
|
|
1669
|
+
return false;
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1538
1672
|
function matchToolPattern(pattern, toolName) {
|
|
1539
1673
|
if (pattern === "*" || pattern === toolName) return true;
|
|
1540
1674
|
if (pattern.endsWith("*")) {
|
|
@@ -1577,7 +1711,7 @@ function isToolAllowed(commandName, policy) {
|
|
|
1577
1711
|
}
|
|
1578
1712
|
return true;
|
|
1579
1713
|
}
|
|
1580
|
-
function enforceToolPolicy(commandName, env = process.env) {
|
|
1714
|
+
function enforceToolPolicy(commandName, env = process.env, options) {
|
|
1581
1715
|
const attemptTokenRaw = env.KXM_ATTEMPT_TOKEN?.trim();
|
|
1582
1716
|
if (attemptTokenRaw) {
|
|
1583
1717
|
const attempt = parseAttemptToken(attemptTokenRaw);
|
|
@@ -1591,13 +1725,51 @@ function enforceToolPolicy(commandName, env = process.env) {
|
|
|
1591
1725
|
detail: `command ${commandName} is denied by attempt tool policy`
|
|
1592
1726
|
};
|
|
1593
1727
|
}
|
|
1728
|
+
if (commandName === "kxm_promote" || commandName === "promote") {
|
|
1729
|
+
const explicitAllow = attempt.toolPolicy?.allow ?? attempt.toolPolicy?.allowedTools;
|
|
1730
|
+
if (!Array.isArray(explicitAllow) || !explicitAllow.includes("kxm_promote") && !explicitAllow.includes("promote") && !explicitAllow.includes("*")) {
|
|
1731
|
+
return {
|
|
1732
|
+
allowed: false,
|
|
1733
|
+
error: "attempt_token_admin_denied",
|
|
1734
|
+
detail: "AttemptToken worker cannot perform operator state promotion without explicit policy grant"
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
if (options?.runId && attempt.runId && options.runId !== attempt.runId) {
|
|
1739
|
+
return {
|
|
1740
|
+
allowed: false,
|
|
1741
|
+
error: "attempt_token_scope_violation",
|
|
1742
|
+
detail: `attempt token runId ${attempt.runId} does not match request runId ${options.runId}`
|
|
1743
|
+
};
|
|
1744
|
+
}
|
|
1594
1745
|
return { allowed: true };
|
|
1595
1746
|
}
|
|
1596
|
-
const
|
|
1597
|
-
if (
|
|
1598
|
-
const session = parseSessionToken(
|
|
1747
|
+
const envSessionRaw = env.KXM_SESSION_TOKEN?.trim();
|
|
1748
|
+
if (envSessionRaw) {
|
|
1749
|
+
const session = parseSessionToken(envSessionRaw);
|
|
1750
|
+
if (!session) {
|
|
1751
|
+
return { allowed: false, error: "session_token_invalid", detail: "KXM_SESSION_TOKEN is malformed or expired" };
|
|
1752
|
+
}
|
|
1753
|
+
if (!isToolAllowed(commandName, session.toolPolicy)) {
|
|
1754
|
+
return {
|
|
1755
|
+
allowed: false,
|
|
1756
|
+
error: "tool_policy_denied",
|
|
1757
|
+
detail: `command ${commandName} is denied by session tool policy`
|
|
1758
|
+
};
|
|
1759
|
+
}
|
|
1760
|
+
return { allowed: true };
|
|
1761
|
+
}
|
|
1762
|
+
const tokenFile = sessionTokenPath(env.KXM_USER_CONFIG_DIR);
|
|
1763
|
+
if (existsSync(tokenFile)) {
|
|
1764
|
+
let tokenRaw;
|
|
1765
|
+
try {
|
|
1766
|
+
tokenRaw = readFileSync(tokenFile, "utf8").trim();
|
|
1767
|
+
} catch {
|
|
1768
|
+
return { allowed: false, error: "session_token_invalid", detail: "Session token file on disk could not be read" };
|
|
1769
|
+
}
|
|
1770
|
+
const session = tokenRaw ? parseSessionToken(tokenRaw) : void 0;
|
|
1599
1771
|
if (!session) {
|
|
1600
|
-
return { allowed: false, error: "session_token_invalid", detail: "
|
|
1772
|
+
return { allowed: false, error: "session_token_invalid", detail: "Session token on disk is malformed or expired" };
|
|
1601
1773
|
}
|
|
1602
1774
|
if (!isToolAllowed(commandName, session.toolPolicy)) {
|
|
1603
1775
|
return {
|
|
@@ -1612,8 +1784,8 @@ function enforceToolPolicy(commandName, env = process.env) {
|
|
|
1612
1784
|
}
|
|
1613
1785
|
|
|
1614
1786
|
// plugins/kxm/src/logger.ts
|
|
1615
|
-
import { appendFileSync, existsSync, mkdirSync, renameSync, statSync, unlinkSync } from "node:fs";
|
|
1616
|
-
import { dirname } from "node:path";
|
|
1787
|
+
import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2, renameSync, statSync, unlinkSync as unlinkSync2 } from "node:fs";
|
|
1788
|
+
import { dirname as dirname2 } from "node:path";
|
|
1617
1789
|
var LOG_LEVEL_PRIORITY = {
|
|
1618
1790
|
debug: 10,
|
|
1619
1791
|
info: 20,
|
|
@@ -1650,10 +1822,10 @@ function redactLogValue(val, key) {
|
|
|
1650
1822
|
function rotateLogFiles(filePath, maxFiles) {
|
|
1651
1823
|
for (let i = maxFiles; i >= 1; i--) {
|
|
1652
1824
|
const current = `${filePath}.${i}`;
|
|
1653
|
-
if (
|
|
1825
|
+
if (existsSync2(current)) {
|
|
1654
1826
|
if (i >= maxFiles) {
|
|
1655
1827
|
try {
|
|
1656
|
-
|
|
1828
|
+
unlinkSync2(current);
|
|
1657
1829
|
} catch {
|
|
1658
1830
|
}
|
|
1659
1831
|
} else {
|
|
@@ -1664,7 +1836,7 @@ function rotateLogFiles(filePath, maxFiles) {
|
|
|
1664
1836
|
}
|
|
1665
1837
|
}
|
|
1666
1838
|
}
|
|
1667
|
-
if (
|
|
1839
|
+
if (existsSync2(filePath)) {
|
|
1668
1840
|
try {
|
|
1669
1841
|
renameSync(filePath, `${filePath}.1`);
|
|
1670
1842
|
} catch {
|
|
@@ -1681,7 +1853,7 @@ function createLogger(options) {
|
|
|
1681
1853
|
const shouldStdout = options.stdout ?? !isDaemon;
|
|
1682
1854
|
const correlationDefaults = options.correlation ?? {};
|
|
1683
1855
|
let currentSize = 0;
|
|
1684
|
-
if (filePath &&
|
|
1856
|
+
if (filePath && existsSync2(filePath)) {
|
|
1685
1857
|
try {
|
|
1686
1858
|
currentSize = statSync(filePath).size;
|
|
1687
1859
|
} catch {
|
|
@@ -1719,7 +1891,7 @@ function createLogger(options) {
|
|
|
1719
1891
|
currentSize = 0;
|
|
1720
1892
|
}
|
|
1721
1893
|
try {
|
|
1722
|
-
|
|
1894
|
+
mkdirSync2(dirname2(filePath), { recursive: true });
|
|
1723
1895
|
appendFileSync(filePath, line, { encoding: "utf8", mode: 384 });
|
|
1724
1896
|
currentSize += lineBytes;
|
|
1725
1897
|
} catch {
|
|
@@ -1789,9 +1961,12 @@ export {
|
|
|
1789
1961
|
WORKER_SCHEMA,
|
|
1790
1962
|
agentWorker,
|
|
1791
1963
|
behavioralConfigHash,
|
|
1964
|
+
clearSessionTokenFromDisk,
|
|
1792
1965
|
compareRoutingRecords,
|
|
1966
|
+
computeDecayedWeight,
|
|
1793
1967
|
createLogger,
|
|
1794
1968
|
enforceToolPolicy,
|
|
1969
|
+
evaluateCircuitBreaker,
|
|
1795
1970
|
formatRoutingReport,
|
|
1796
1971
|
gateWorker,
|
|
1797
1972
|
generateRoutingReport,
|
|
@@ -1800,6 +1975,7 @@ export {
|
|
|
1800
1975
|
getPiToolDefinitions,
|
|
1801
1976
|
groupByBehavior,
|
|
1802
1977
|
isQuotaExhausted,
|
|
1978
|
+
isSessionTokenExpired,
|
|
1803
1979
|
isToolAllowed,
|
|
1804
1980
|
looksLikeSecret,
|
|
1805
1981
|
mintAttemptToken,
|
|
@@ -1813,11 +1989,15 @@ export {
|
|
|
1813
1989
|
parseRoutingRecord,
|
|
1814
1990
|
parseRoutingRecordV2,
|
|
1815
1991
|
parseSessionToken,
|
|
1992
|
+
persistSessionTokenToDisk,
|
|
1993
|
+
readSessionTokenFromDisk,
|
|
1816
1994
|
reconcileInbox,
|
|
1817
1995
|
redactLogValue,
|
|
1818
1996
|
redactSecrets,
|
|
1819
1997
|
redactStringList,
|
|
1820
1998
|
requireString,
|
|
1821
1999
|
rotateLogFiles,
|
|
2000
|
+
sessionTokenPath,
|
|
2001
|
+
timingSafeStringCompare,
|
|
1822
2002
|
workerResult
|
|
1823
2003
|
};
|