obsidian-tc 1.0.1 → 1.0.2
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/cli.js +99 -31
- package/dist/index.js +94 -28
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -30672,7 +30672,7 @@ var MUTATING_FAMILIES = ["write", "delete", "bulk", "execute"];
|
|
|
30672
30672
|
function isMutatingScope(scope) {
|
|
30673
30673
|
return MUTATING_FAMILIES.includes(parseScope(scope).family);
|
|
30674
30674
|
}
|
|
30675
|
-
var SCOPE_CLASS_PRECEDENCE = ["bulk", "execute", "admin", "write", "read"];
|
|
30675
|
+
var SCOPE_CLASS_PRECEDENCE = ["bulk", "execute", "admin", "delete", "write", "read"];
|
|
30676
30676
|
function scopeClassOf(requiredScopes) {
|
|
30677
30677
|
const families = new Set(requiredScopes.map((s) => parseScope(s).family));
|
|
30678
30678
|
for (const cls of SCOPE_CLASS_PRECEDENCE)
|
|
@@ -34653,6 +34653,29 @@ var coerce = {
|
|
|
34653
34653
|
date: (arg) => ZodDate.create({ ...arg, coerce: true })
|
|
34654
34654
|
};
|
|
34655
34655
|
var NEVER = INVALID;
|
|
34656
|
+
// ../shared/src/net-host.ts
|
|
34657
|
+
function normalizeHostForBind(host) {
|
|
34658
|
+
return host.trim().toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
|
|
34659
|
+
}
|
|
34660
|
+
function isStrictIpv4(h) {
|
|
34661
|
+
const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(h);
|
|
34662
|
+
if (!m)
|
|
34663
|
+
return false;
|
|
34664
|
+
return m.slice(1).every((octet) => Number(octet) <= 255);
|
|
34665
|
+
}
|
|
34666
|
+
function isLoopbackHost(host) {
|
|
34667
|
+
const h = normalizeHostForBind(host);
|
|
34668
|
+
if (h === "localhost" || h === "::1")
|
|
34669
|
+
return true;
|
|
34670
|
+
if (isStrictIpv4(h))
|
|
34671
|
+
return h.startsWith("127.");
|
|
34672
|
+
if (h.startsWith("::ffff:")) {
|
|
34673
|
+
const v4 = h.slice("::ffff:".length);
|
|
34674
|
+
return isStrictIpv4(v4) && v4.startsWith("127.");
|
|
34675
|
+
}
|
|
34676
|
+
return false;
|
|
34677
|
+
}
|
|
34678
|
+
|
|
34656
34679
|
// ../shared/src/config.schema.ts
|
|
34657
34680
|
var VaultBridgesConfigSchema = exports_external.object({
|
|
34658
34681
|
timeoutMs: exports_external.number().int().positive().default(5000),
|
|
@@ -34688,7 +34711,7 @@ var VaultConfigSchema = exports_external.object({
|
|
|
34688
34711
|
workspace: VaultWorkspaceConfigSchema.optional()
|
|
34689
34712
|
});
|
|
34690
34713
|
var AuthConfigSchema = exports_external.object({
|
|
34691
|
-
mode: exports_external.enum(["none", "jwt"
|
|
34714
|
+
mode: exports_external.enum(["none", "jwt"]).default("none"),
|
|
34692
34715
|
jwtSecret: exports_external.string().min(32).optional(),
|
|
34693
34716
|
tokenTtlSeconds: exports_external.number().int().positive().default(86400)
|
|
34694
34717
|
}).refine((c) => c.mode !== "jwt" || !!c.jwtSecret, {
|
|
@@ -34770,7 +34793,7 @@ var PlurConfigSchema = exports_external.object({
|
|
|
34770
34793
|
apiPrefix: exports_external.string().default(""),
|
|
34771
34794
|
timeoutMs: exports_external.number().int().positive().default(5000)
|
|
34772
34795
|
});
|
|
34773
|
-
var
|
|
34796
|
+
var ServerConfigObject = exports_external.object({
|
|
34774
34797
|
cacheDir: exports_external.string().default(".obsidian-tc"),
|
|
34775
34798
|
vaults: exports_external.array(VaultConfigSchema).min(1),
|
|
34776
34799
|
plur: PlurConfigSchema.optional(),
|
|
@@ -34784,6 +34807,16 @@ var ServerConfigSchema = exports_external.object({
|
|
|
34784
34807
|
idempotencyTtlSeconds: exports_external.number().int().positive().default(86400),
|
|
34785
34808
|
elicitTtlSeconds: exports_external.number().int().positive().default(300)
|
|
34786
34809
|
});
|
|
34810
|
+
var ServerConfigSchema = ServerConfigObject.superRefine((cfg, ctx) => {
|
|
34811
|
+
const http = cfg.transports.http;
|
|
34812
|
+
if (http.enabled && cfg.auth.mode === "none" && !isLoopbackHost(http.host)) {
|
|
34813
|
+
ctx.addIssue({
|
|
34814
|
+
code: exports_external.ZodIssueCode.custom,
|
|
34815
|
+
path: ["transports", "http", "host"],
|
|
34816
|
+
message: `refusing to expose an unauthenticated server: transports.http.enabled is true with host "${http.host}" (non-loopback) while auth.mode is "none". Set auth.mode to "jwt" (with jwtSecret) or bind transports.http.host to a loopback address (127.0.0.1, ::1, localhost).`
|
|
34817
|
+
});
|
|
34818
|
+
}
|
|
34819
|
+
});
|
|
34787
34820
|
// ../shared/src/morgiana.schema.ts
|
|
34788
34821
|
var MORGIANA_EVENT_TYPES = [
|
|
34789
34822
|
"tc.tool.call.completed",
|
|
@@ -35241,10 +35274,12 @@ function jsBm25Score(tf, docLen, avgDocLen, docFreq, docCount) {
|
|
|
35241
35274
|
const denom = tf + k1 * (1 - b + b * (docLen / Math.max(avgDocLen, 1)));
|
|
35242
35275
|
return idf * (tf * (k1 + 1)) / denom;
|
|
35243
35276
|
}
|
|
35244
|
-
|
|
35245
|
-
|
|
35277
|
+
var NATIVE_PKG = ["@the-40-thieves", "obsidian-tc-native"].join("/");
|
|
35278
|
+
function loadNative(env = process.env, requireFn = createRequire2(import.meta.url)) {
|
|
35279
|
+
if (env.OBSIDIAN_TC_FORCE_JS_FALLBACK === "1")
|
|
35280
|
+
return null;
|
|
35246
35281
|
try {
|
|
35247
|
-
const mod =
|
|
35282
|
+
const mod = requireFn(NATIVE_PKG);
|
|
35248
35283
|
if (typeof mod.cosineSimilarity === "function" && typeof mod.tokenize === "function" && typeof mod.bm25Score === "function") {
|
|
35249
35284
|
return mod;
|
|
35250
35285
|
}
|
|
@@ -35387,6 +35422,7 @@ class TokenBucket {
|
|
|
35387
35422
|
var DEFAULT_THROTTLE_TIERS = {
|
|
35388
35423
|
read: { perMinute: 600, burst: 100 },
|
|
35389
35424
|
write: { perMinute: 60, burst: 20 },
|
|
35425
|
+
delete: { perMinute: 60, burst: 20 },
|
|
35390
35426
|
bulk: { perMinute: 10, burst: 3 },
|
|
35391
35427
|
execute: { perMinute: 5, burst: 1 },
|
|
35392
35428
|
admin: { perMinute: 5, burst: 1 }
|
|
@@ -35397,8 +35433,15 @@ class RateLimiter {
|
|
|
35397
35433
|
tiers;
|
|
35398
35434
|
buckets = new Map;
|
|
35399
35435
|
hits = new Map;
|
|
35400
|
-
|
|
35436
|
+
idleTtlMs;
|
|
35437
|
+
maxBuckets;
|
|
35438
|
+
sweepIntervalMs;
|
|
35439
|
+
lastSweepMs = null;
|
|
35440
|
+
constructor(tiers = DEFAULT_THROTTLE_TIERS, opts = {}) {
|
|
35401
35441
|
this.tiers = tiers;
|
|
35442
|
+
this.idleTtlMs = opts.idleTtlMs ?? 600000;
|
|
35443
|
+
this.maxBuckets = opts.maxBuckets ?? 1e4;
|
|
35444
|
+
this.sweepIntervalMs = opts.sweepIntervalMs ?? 60000;
|
|
35402
35445
|
}
|
|
35403
35446
|
check(callerHashValue, scopeClass, vaultId, nowMs, n = 1) {
|
|
35404
35447
|
const tier = this.tiers[scopeClass];
|
|
@@ -35406,20 +35449,26 @@ class RateLimiter {
|
|
|
35406
35449
|
return { ok: true, scopeClass, retryAfterSeconds: 0, currentBurst: -1, currentRate: -1 };
|
|
35407
35450
|
}
|
|
35408
35451
|
const key = `${callerHashValue}|${scopeClass}|${vaultId}`;
|
|
35409
|
-
let
|
|
35410
|
-
if (!
|
|
35411
|
-
|
|
35412
|
-
|
|
35413
|
-
|
|
35414
|
-
|
|
35415
|
-
|
|
35416
|
-
|
|
35452
|
+
let entry = this.buckets.get(key);
|
|
35453
|
+
if (!entry) {
|
|
35454
|
+
entry = {
|
|
35455
|
+
bucket: new TokenBucket({
|
|
35456
|
+
capacity: tier.burst,
|
|
35457
|
+
refillTokens: tier.perMinute,
|
|
35458
|
+
intervalMs: INTERVAL_MS
|
|
35459
|
+
}),
|
|
35460
|
+
fullRefillMs: tier.perMinute > 0 ? Math.ceil(tier.burst * INTERVAL_MS / tier.perMinute) : 0,
|
|
35461
|
+
lastSeenMs: nowMs
|
|
35462
|
+
};
|
|
35463
|
+
this.buckets.set(key, entry);
|
|
35417
35464
|
}
|
|
35418
|
-
|
|
35465
|
+
entry.lastSeenMs = nowMs;
|
|
35466
|
+
const res = entry.bucket.tryRemove(n, nowMs);
|
|
35419
35467
|
if (!res.ok) {
|
|
35420
35468
|
const hk = `${vaultId}|${scopeClass}`;
|
|
35421
35469
|
this.hits.set(hk, (this.hits.get(hk) ?? 0) + 1);
|
|
35422
35470
|
}
|
|
35471
|
+
this.sweep(nowMs);
|
|
35423
35472
|
return {
|
|
35424
35473
|
ok: res.ok,
|
|
35425
35474
|
scopeClass,
|
|
@@ -35428,6 +35477,26 @@ class RateLimiter {
|
|
|
35428
35477
|
currentRate: tier.perMinute
|
|
35429
35478
|
};
|
|
35430
35479
|
}
|
|
35480
|
+
sweep(nowMs) {
|
|
35481
|
+
if (this.lastSweepMs !== null && nowMs - this.lastSweepMs < this.sweepIntervalMs)
|
|
35482
|
+
return;
|
|
35483
|
+
this.lastSweepMs = nowMs;
|
|
35484
|
+
for (const [key, e] of this.buckets) {
|
|
35485
|
+
if (nowMs - e.lastSeenMs >= this.idleTtlMs)
|
|
35486
|
+
this.buckets.delete(key);
|
|
35487
|
+
}
|
|
35488
|
+
if (this.buckets.size <= this.maxBuckets)
|
|
35489
|
+
return;
|
|
35490
|
+
const evictable = [...this.buckets.entries()].filter(([, e]) => nowMs - e.lastSeenMs >= e.fullRefillMs).sort((a, b) => a[1].lastSeenMs - b[1].lastSeenMs);
|
|
35491
|
+
for (const [key] of evictable) {
|
|
35492
|
+
if (this.buckets.size <= this.maxBuckets)
|
|
35493
|
+
break;
|
|
35494
|
+
this.buckets.delete(key);
|
|
35495
|
+
}
|
|
35496
|
+
}
|
|
35497
|
+
get bucketCount() {
|
|
35498
|
+
return this.buckets.size;
|
|
35499
|
+
}
|
|
35431
35500
|
snapshot() {
|
|
35432
35501
|
return [...this.hits.entries()].map(([k, hits]) => {
|
|
35433
35502
|
const sep = k.indexOf("|");
|
|
@@ -35444,6 +35513,7 @@ function callStatusForError(code) {
|
|
|
35444
35513
|
switch (code) {
|
|
35445
35514
|
case "unauthorized":
|
|
35446
35515
|
case "forbidden":
|
|
35516
|
+
case "acl_denied":
|
|
35447
35517
|
case "elicit_required":
|
|
35448
35518
|
case "throttled":
|
|
35449
35519
|
return "denied";
|
|
@@ -35525,6 +35595,7 @@ class ToolRegistry {
|
|
|
35525
35595
|
}
|
|
35526
35596
|
switch (result2.error.code) {
|
|
35527
35597
|
case "forbidden":
|
|
35598
|
+
case "acl_denied":
|
|
35528
35599
|
this.relay(ctx.vaultId, "tc.acl.denied", data);
|
|
35529
35600
|
break;
|
|
35530
35601
|
case "overflow":
|
|
@@ -35672,7 +35743,7 @@ class ToolRegistry {
|
|
|
35672
35743
|
const duration = Math.max(0, now() - start);
|
|
35673
35744
|
audit("error", duration, 0, error.code);
|
|
35674
35745
|
this.meter((m) => {
|
|
35675
|
-
if (error.code === "forbidden")
|
|
35746
|
+
if (error.code === "forbidden" || error.code === "acl_denied")
|
|
35676
35747
|
m.incAclDenied(ctx.vaultId, scopeClass, error.code);
|
|
35677
35748
|
m.observeToolCall(ctx.vaultId, name, callStatusForError(error.code), duration / 1000, 0);
|
|
35678
35749
|
});
|
|
@@ -56891,20 +56962,17 @@ async function resolveAuth(header, auth) {
|
|
|
56891
56962
|
if (auth.mode === "none") {
|
|
56892
56963
|
return { ok: true, caller: "http-local", scopes: new Set(["*"]) };
|
|
56893
56964
|
}
|
|
56894
|
-
|
|
56895
|
-
|
|
56896
|
-
|
|
56897
|
-
|
|
56898
|
-
|
|
56899
|
-
|
|
56900
|
-
|
|
56901
|
-
|
|
56902
|
-
|
|
56903
|
-
|
|
56904
|
-
return { ok: false, status: 401, reason: "invalid or expired token" };
|
|
56905
|
-
}
|
|
56965
|
+
const token = bearer2(header);
|
|
56966
|
+
if (!token)
|
|
56967
|
+
return { ok: false, status: 401, reason: "missing bearer token" };
|
|
56968
|
+
if (!auth.jwtSecret)
|
|
56969
|
+
return { ok: false, status: 500, reason: "jwt mode misconfigured: no secret" };
|
|
56970
|
+
try {
|
|
56971
|
+
const id = await verifyJwt(token, auth.jwtSecret);
|
|
56972
|
+
return { ok: true, caller: id.caller, scopes: id.scopes };
|
|
56973
|
+
} catch {
|
|
56974
|
+
return { ok: false, status: 401, reason: "invalid or expired token" };
|
|
56906
56975
|
}
|
|
56907
|
-
return { ok: false, status: 501, reason: `auth mode '${auth.mode}' is not implemented` };
|
|
56908
56976
|
}
|
|
56909
56977
|
function createHttpApp(opts) {
|
|
56910
56978
|
const app = new Hono2;
|
|
@@ -56956,7 +57024,7 @@ function createHttpApp(opts) {
|
|
|
56956
57024
|
function startHttp(opts) {
|
|
56957
57025
|
const app = createHttpApp(opts);
|
|
56958
57026
|
return new Promise((resolve2) => {
|
|
56959
|
-
const server = serve({ fetch: app.fetch, hostname: opts.host, port: opts.port }, (info) => {
|
|
57027
|
+
const server = serve({ fetch: app.fetch, hostname: normalizeHostForBind(opts.host), port: opts.port }, (info) => {
|
|
56960
57028
|
resolve2({
|
|
56961
57029
|
port: info.port,
|
|
56962
57030
|
close: () => new Promise((done) => {
|
package/dist/index.js
CHANGED
|
@@ -20730,7 +20730,7 @@ var MUTATING_FAMILIES = ["write", "delete", "bulk", "execute"];
|
|
|
20730
20730
|
function isMutatingScope(scope) {
|
|
20731
20731
|
return MUTATING_FAMILIES.includes(parseScope(scope).family);
|
|
20732
20732
|
}
|
|
20733
|
-
var SCOPE_CLASS_PRECEDENCE = ["bulk", "execute", "admin", "write", "read"];
|
|
20733
|
+
var SCOPE_CLASS_PRECEDENCE = ["bulk", "execute", "admin", "delete", "write", "read"];
|
|
20734
20734
|
function scopeClassOf(requiredScopes) {
|
|
20735
20735
|
const families = new Set(requiredScopes.map((s) => parseScope(s).family));
|
|
20736
20736
|
for (const cls of SCOPE_CLASS_PRECEDENCE)
|
|
@@ -24711,6 +24711,29 @@ var coerce = {
|
|
|
24711
24711
|
date: (arg) => ZodDate.create({ ...arg, coerce: true })
|
|
24712
24712
|
};
|
|
24713
24713
|
var NEVER = INVALID;
|
|
24714
|
+
// ../shared/src/net-host.ts
|
|
24715
|
+
function normalizeHostForBind(host) {
|
|
24716
|
+
return host.trim().toLowerCase().replace(/^\[/, "").replace(/\]$/, "");
|
|
24717
|
+
}
|
|
24718
|
+
function isStrictIpv4(h) {
|
|
24719
|
+
const m = /^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/.exec(h);
|
|
24720
|
+
if (!m)
|
|
24721
|
+
return false;
|
|
24722
|
+
return m.slice(1).every((octet) => Number(octet) <= 255);
|
|
24723
|
+
}
|
|
24724
|
+
function isLoopbackHost(host) {
|
|
24725
|
+
const h = normalizeHostForBind(host);
|
|
24726
|
+
if (h === "localhost" || h === "::1")
|
|
24727
|
+
return true;
|
|
24728
|
+
if (isStrictIpv4(h))
|
|
24729
|
+
return h.startsWith("127.");
|
|
24730
|
+
if (h.startsWith("::ffff:")) {
|
|
24731
|
+
const v4 = h.slice("::ffff:".length);
|
|
24732
|
+
return isStrictIpv4(v4) && v4.startsWith("127.");
|
|
24733
|
+
}
|
|
24734
|
+
return false;
|
|
24735
|
+
}
|
|
24736
|
+
|
|
24714
24737
|
// ../shared/src/config.schema.ts
|
|
24715
24738
|
var VaultBridgesConfigSchema = exports_external.object({
|
|
24716
24739
|
timeoutMs: exports_external.number().int().positive().default(5000),
|
|
@@ -24746,7 +24769,7 @@ var VaultConfigSchema = exports_external.object({
|
|
|
24746
24769
|
workspace: VaultWorkspaceConfigSchema.optional()
|
|
24747
24770
|
});
|
|
24748
24771
|
var AuthConfigSchema = exports_external.object({
|
|
24749
|
-
mode: exports_external.enum(["none", "jwt"
|
|
24772
|
+
mode: exports_external.enum(["none", "jwt"]).default("none"),
|
|
24750
24773
|
jwtSecret: exports_external.string().min(32).optional(),
|
|
24751
24774
|
tokenTtlSeconds: exports_external.number().int().positive().default(86400)
|
|
24752
24775
|
}).refine((c) => c.mode !== "jwt" || !!c.jwtSecret, {
|
|
@@ -24828,7 +24851,7 @@ var PlurConfigSchema = exports_external.object({
|
|
|
24828
24851
|
apiPrefix: exports_external.string().default(""),
|
|
24829
24852
|
timeoutMs: exports_external.number().int().positive().default(5000)
|
|
24830
24853
|
});
|
|
24831
|
-
var
|
|
24854
|
+
var ServerConfigObject = exports_external.object({
|
|
24832
24855
|
cacheDir: exports_external.string().default(".obsidian-tc"),
|
|
24833
24856
|
vaults: exports_external.array(VaultConfigSchema).min(1),
|
|
24834
24857
|
plur: PlurConfigSchema.optional(),
|
|
@@ -24842,6 +24865,16 @@ var ServerConfigSchema = exports_external.object({
|
|
|
24842
24865
|
idempotencyTtlSeconds: exports_external.number().int().positive().default(86400),
|
|
24843
24866
|
elicitTtlSeconds: exports_external.number().int().positive().default(300)
|
|
24844
24867
|
});
|
|
24868
|
+
var ServerConfigSchema = ServerConfigObject.superRefine((cfg, ctx) => {
|
|
24869
|
+
const http = cfg.transports.http;
|
|
24870
|
+
if (http.enabled && cfg.auth.mode === "none" && !isLoopbackHost(http.host)) {
|
|
24871
|
+
ctx.addIssue({
|
|
24872
|
+
code: exports_external.ZodIssueCode.custom,
|
|
24873
|
+
path: ["transports", "http", "host"],
|
|
24874
|
+
message: `refusing to expose an unauthenticated server: transports.http.enabled is true with host "${http.host}" (non-loopback) while auth.mode is "none". Set auth.mode to "jwt" (with jwtSecret) or bind transports.http.host to a loopback address (127.0.0.1, ::1, localhost).`
|
|
24875
|
+
});
|
|
24876
|
+
}
|
|
24877
|
+
});
|
|
24845
24878
|
// ../shared/src/morgiana.schema.ts
|
|
24846
24879
|
var MORGIANA_EVENT_TYPES = [
|
|
24847
24880
|
"tc.tool.call.completed",
|
|
@@ -25124,6 +25157,7 @@ class TokenBucket {
|
|
|
25124
25157
|
var DEFAULT_THROTTLE_TIERS = {
|
|
25125
25158
|
read: { perMinute: 600, burst: 100 },
|
|
25126
25159
|
write: { perMinute: 60, burst: 20 },
|
|
25160
|
+
delete: { perMinute: 60, burst: 20 },
|
|
25127
25161
|
bulk: { perMinute: 10, burst: 3 },
|
|
25128
25162
|
execute: { perMinute: 5, burst: 1 },
|
|
25129
25163
|
admin: { perMinute: 5, burst: 1 }
|
|
@@ -25134,8 +25168,15 @@ class RateLimiter {
|
|
|
25134
25168
|
tiers;
|
|
25135
25169
|
buckets = new Map;
|
|
25136
25170
|
hits = new Map;
|
|
25137
|
-
|
|
25171
|
+
idleTtlMs;
|
|
25172
|
+
maxBuckets;
|
|
25173
|
+
sweepIntervalMs;
|
|
25174
|
+
lastSweepMs = null;
|
|
25175
|
+
constructor(tiers = DEFAULT_THROTTLE_TIERS, opts = {}) {
|
|
25138
25176
|
this.tiers = tiers;
|
|
25177
|
+
this.idleTtlMs = opts.idleTtlMs ?? 600000;
|
|
25178
|
+
this.maxBuckets = opts.maxBuckets ?? 1e4;
|
|
25179
|
+
this.sweepIntervalMs = opts.sweepIntervalMs ?? 60000;
|
|
25139
25180
|
}
|
|
25140
25181
|
check(callerHashValue, scopeClass, vaultId, nowMs, n = 1) {
|
|
25141
25182
|
const tier = this.tiers[scopeClass];
|
|
@@ -25143,20 +25184,26 @@ class RateLimiter {
|
|
|
25143
25184
|
return { ok: true, scopeClass, retryAfterSeconds: 0, currentBurst: -1, currentRate: -1 };
|
|
25144
25185
|
}
|
|
25145
25186
|
const key = `${callerHashValue}|${scopeClass}|${vaultId}`;
|
|
25146
|
-
let
|
|
25147
|
-
if (!
|
|
25148
|
-
|
|
25149
|
-
|
|
25150
|
-
|
|
25151
|
-
|
|
25152
|
-
|
|
25153
|
-
|
|
25187
|
+
let entry = this.buckets.get(key);
|
|
25188
|
+
if (!entry) {
|
|
25189
|
+
entry = {
|
|
25190
|
+
bucket: new TokenBucket({
|
|
25191
|
+
capacity: tier.burst,
|
|
25192
|
+
refillTokens: tier.perMinute,
|
|
25193
|
+
intervalMs: INTERVAL_MS
|
|
25194
|
+
}),
|
|
25195
|
+
fullRefillMs: tier.perMinute > 0 ? Math.ceil(tier.burst * INTERVAL_MS / tier.perMinute) : 0,
|
|
25196
|
+
lastSeenMs: nowMs
|
|
25197
|
+
};
|
|
25198
|
+
this.buckets.set(key, entry);
|
|
25154
25199
|
}
|
|
25155
|
-
|
|
25200
|
+
entry.lastSeenMs = nowMs;
|
|
25201
|
+
const res = entry.bucket.tryRemove(n, nowMs);
|
|
25156
25202
|
if (!res.ok) {
|
|
25157
25203
|
const hk = `${vaultId}|${scopeClass}`;
|
|
25158
25204
|
this.hits.set(hk, (this.hits.get(hk) ?? 0) + 1);
|
|
25159
25205
|
}
|
|
25206
|
+
this.sweep(nowMs);
|
|
25160
25207
|
return {
|
|
25161
25208
|
ok: res.ok,
|
|
25162
25209
|
scopeClass,
|
|
@@ -25165,6 +25212,26 @@ class RateLimiter {
|
|
|
25165
25212
|
currentRate: tier.perMinute
|
|
25166
25213
|
};
|
|
25167
25214
|
}
|
|
25215
|
+
sweep(nowMs) {
|
|
25216
|
+
if (this.lastSweepMs !== null && nowMs - this.lastSweepMs < this.sweepIntervalMs)
|
|
25217
|
+
return;
|
|
25218
|
+
this.lastSweepMs = nowMs;
|
|
25219
|
+
for (const [key, e] of this.buckets) {
|
|
25220
|
+
if (nowMs - e.lastSeenMs >= this.idleTtlMs)
|
|
25221
|
+
this.buckets.delete(key);
|
|
25222
|
+
}
|
|
25223
|
+
if (this.buckets.size <= this.maxBuckets)
|
|
25224
|
+
return;
|
|
25225
|
+
const evictable = [...this.buckets.entries()].filter(([, e]) => nowMs - e.lastSeenMs >= e.fullRefillMs).sort((a, b) => a[1].lastSeenMs - b[1].lastSeenMs);
|
|
25226
|
+
for (const [key] of evictable) {
|
|
25227
|
+
if (this.buckets.size <= this.maxBuckets)
|
|
25228
|
+
break;
|
|
25229
|
+
this.buckets.delete(key);
|
|
25230
|
+
}
|
|
25231
|
+
}
|
|
25232
|
+
get bucketCount() {
|
|
25233
|
+
return this.buckets.size;
|
|
25234
|
+
}
|
|
25168
25235
|
snapshot() {
|
|
25169
25236
|
return [...this.hits.entries()].map(([k, hits]) => {
|
|
25170
25237
|
const sep = k.indexOf("|");
|
|
@@ -25181,6 +25248,7 @@ function callStatusForError(code) {
|
|
|
25181
25248
|
switch (code) {
|
|
25182
25249
|
case "unauthorized":
|
|
25183
25250
|
case "forbidden":
|
|
25251
|
+
case "acl_denied":
|
|
25184
25252
|
case "elicit_required":
|
|
25185
25253
|
case "throttled":
|
|
25186
25254
|
return "denied";
|
|
@@ -25262,6 +25330,7 @@ class ToolRegistry {
|
|
|
25262
25330
|
}
|
|
25263
25331
|
switch (result2.error.code) {
|
|
25264
25332
|
case "forbidden":
|
|
25333
|
+
case "acl_denied":
|
|
25265
25334
|
this.relay(ctx.vaultId, "tc.acl.denied", data);
|
|
25266
25335
|
break;
|
|
25267
25336
|
case "overflow":
|
|
@@ -25409,7 +25478,7 @@ class ToolRegistry {
|
|
|
25409
25478
|
const duration = Math.max(0, now() - start);
|
|
25410
25479
|
audit("error", duration, 0, error.code);
|
|
25411
25480
|
this.meter((m) => {
|
|
25412
|
-
if (error.code === "forbidden")
|
|
25481
|
+
if (error.code === "forbidden" || error.code === "acl_denied")
|
|
25413
25482
|
m.incAclDenied(ctx.vaultId, scopeClass, error.code);
|
|
25414
25483
|
m.observeToolCall(ctx.vaultId, name, callStatusForError(error.code), duration / 1000, 0);
|
|
25415
25484
|
});
|
|
@@ -39712,20 +39781,17 @@ async function resolveAuth(header, auth) {
|
|
|
39712
39781
|
if (auth.mode === "none") {
|
|
39713
39782
|
return { ok: true, caller: "http-local", scopes: new Set(["*"]) };
|
|
39714
39783
|
}
|
|
39715
|
-
|
|
39716
|
-
|
|
39717
|
-
|
|
39718
|
-
|
|
39719
|
-
|
|
39720
|
-
|
|
39721
|
-
|
|
39722
|
-
|
|
39723
|
-
|
|
39724
|
-
|
|
39725
|
-
return { ok: false, status: 401, reason: "invalid or expired token" };
|
|
39726
|
-
}
|
|
39784
|
+
const token = bearer(header);
|
|
39785
|
+
if (!token)
|
|
39786
|
+
return { ok: false, status: 401, reason: "missing bearer token" };
|
|
39787
|
+
if (!auth.jwtSecret)
|
|
39788
|
+
return { ok: false, status: 500, reason: "jwt mode misconfigured: no secret" };
|
|
39789
|
+
try {
|
|
39790
|
+
const id = await verifyJwt(token, auth.jwtSecret);
|
|
39791
|
+
return { ok: true, caller: id.caller, scopes: id.scopes };
|
|
39792
|
+
} catch {
|
|
39793
|
+
return { ok: false, status: 401, reason: "invalid or expired token" };
|
|
39727
39794
|
}
|
|
39728
|
-
return { ok: false, status: 501, reason: `auth mode '${auth.mode}' is not implemented` };
|
|
39729
39795
|
}
|
|
39730
39796
|
function createHttpApp(opts) {
|
|
39731
39797
|
const app = new Hono2;
|
|
@@ -39777,7 +39843,7 @@ function createHttpApp(opts) {
|
|
|
39777
39843
|
function startHttp(opts) {
|
|
39778
39844
|
const app = createHttpApp(opts);
|
|
39779
39845
|
return new Promise((resolve) => {
|
|
39780
|
-
const server = serve({ fetch: app.fetch, hostname: opts.host, port: opts.port }, (info) => {
|
|
39846
|
+
const server = serve({ fetch: app.fetch, hostname: normalizeHostForBind(opts.host), port: opts.port }, (info) => {
|
|
39781
39847
|
resolve({
|
|
39782
39848
|
port: info.port,
|
|
39783
39849
|
close: () => new Promise((done) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "obsidian-tc",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "MCP server for Obsidian — comprehensive tool surface for humans and autonomous agents",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@hono/node-server": "^2.0.5",
|
|
21
21
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
22
|
-
"@the-40-thieves/obsidian-tc-native": "1.0.
|
|
23
|
-
"@the-40-thieves/obsidian-tc-shared": "1.0.
|
|
22
|
+
"@the-40-thieves/obsidian-tc-native": "1.0.2",
|
|
23
|
+
"@the-40-thieves/obsidian-tc-shared": "1.0.2",
|
|
24
24
|
"@opentelemetry/api": "^1.9.1",
|
|
25
25
|
"@opentelemetry/exporter-trace-otlp-http": "^0.219.0",
|
|
26
26
|
"@opentelemetry/resources": "^2.8.0",
|