@sparkelf/dsh-knowledge-base 0.1.21 → 0.1.22

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/lib/index.js CHANGED
@@ -14,11 +14,17 @@ import { DatabaseSync } from "node:sqlite";
14
14
  * Model-facing management tools for the knowledge base and the workflow plaza.
15
15
  *
16
16
  * The UI already exposes these operations. This module gives the model the same
17
- * operations without re-implementing authorization: every tool reads the backend
18
- * review context (roles + department scope) and refuses with a structured 403 before
19
- * touching the host. Writes additionally require an explicit human authorization flag,
20
- * because a model deciding on its own must never be indistinguishable from an operator
21
- * approving a review.
17
+ * operations without re-implementing authorization: which tools exist is decided
18
+ * once from the caller's review context by `toolDisclosure.ts`, so a tool body does
19
+ * not re-read the permission tree on every call. The context is still read where it
20
+ * decides WHICH rows a visible tool may return (department scope, own audit trail)
21
+ * and for the actor recorded in an audit line. Writes additionally require an
22
+ * explicit human authorization flag, because a model deciding on its own must never
23
+ * be indistinguishable from an operator approving a review.
24
+ *
25
+ * DataOps remains the final authority: every host call below reaches a backend
26
+ * route that enforces the JWT's current roles and grants, so a stale disclosure can
27
+ * cause a refusal but never an unauthorized operation.
22
28
  *
23
29
  * Secrets are write-only through the UI; these tools deliberately reject secret-shaped
24
30
  * fields and redact them on reads.
@@ -99,10 +105,6 @@ function defaultPermissionsForRoles(roleKeys) {
99
105
  function hasPermission(context, permission) {
100
106
  return context.permissions.includes(permission);
101
107
  }
102
- /** Require one backend permission key; a denial is a structured 403. */
103
- function requirePermission$1(context, permission, action) {
104
- if (!hasPermission(context, permission)) throw new ManagementToolError("forbidden", `缺少权限 ${permission}${action === void 0 ? "" : "(" + action + ")"}`);
105
- }
106
108
  /** Global administrators: all resources and all review queues. */
107
109
  function isGlobalAdmin(context) {
108
110
  return context.review.canReviewGlobal === true || context.dataScope.all === true || context.roleCodes.some((code) => code === "SUPER_ADMIN" || code === "ADMIN");
@@ -217,7 +219,6 @@ const createManagementTools = (host) => [
217
219
  parameters: {},
218
220
  output: jsonOutput$1,
219
221
  execute: async () => run$2(async () => {
220
- requirePermission$1(await host.context(), "kb:config:manage");
221
222
  return { config: redactSecrets(await host.kbConfigGet()) };
222
223
  })
223
224
  }),
@@ -235,7 +236,6 @@ const createManagementTools = (host) => [
235
236
  },
236
237
  output: jsonOutput$1,
237
238
  execute: async (args) => run$2(async () => {
238
- requirePermission$1(await host.context(), "kb:config:manage");
239
239
  requireConfirmed(args.authorization);
240
240
  assertNoSecretFields(args.patch);
241
241
  return { config: redactSecrets(await host.kbConfigUpdate(args.patch)) };
@@ -248,7 +248,6 @@ const createManagementTools = (host) => [
248
248
  output: jsonOutput$1,
249
249
  execute: async () => run$2(async () => {
250
250
  const context = await host.context();
251
- requirePermission$1(context, "kb:grant:manage");
252
251
  const grants = await host.kbGrantList();
253
252
  return { grants: isGlobalAdmin(context) ? grants : grants.filter((grant) => isPathInDepartmentScope(context, grant.relPath)) };
254
253
  })
@@ -281,9 +280,7 @@ const createManagementTools = (host) => [
281
280
  },
282
281
  output: jsonOutput$1,
283
282
  execute: async (args) => run$2(async () => {
284
- const context = await host.context();
285
- requirePermission$1(context, "kb:grant:manage");
286
- requirePathScope(context, args.relPath);
283
+ requirePathScope(await host.context(), args.relPath);
287
284
  requireConfirmed(args.authorization);
288
285
  if (args.relPath.trim() === "" || args.principalId.trim() === "") throw new ManagementToolError("invalid", "relPath and principalId are required");
289
286
  return { grant: await host.kbGrantSet(args) };
@@ -321,7 +318,6 @@ const createManagementTools = (host) => [
321
318
  output: jsonOutput$1,
322
319
  execute: async (args) => run$2(async () => {
323
320
  const context = await host.context();
324
- requirePermission$1(context, "kb:audit:read");
325
321
  const entries = await host.kbAuditList({
326
322
  ...args,
327
323
  limit: Math.max(1, Math.min(500, args.limit ?? 100))
@@ -336,7 +332,6 @@ const createManagementTools = (host) => [
336
332
  output: jsonOutput$1,
337
333
  execute: async () => run$2(async () => {
338
334
  const context = await host.context();
339
- requirePermission$1(context, "kb:review:decide");
340
335
  const pending = await host.kbReviewList();
341
336
  return { pending: isGlobalAdmin(context) ? pending : pending.filter((item) => isPathInDepartmentScope(context, item.relPath)) };
342
337
  })
@@ -364,7 +359,6 @@ const createManagementTools = (host) => [
364
359
  output: jsonOutput$1,
365
360
  execute: async (args) => run$2(async () => {
366
361
  const context = await host.context();
367
- requirePermission$1(context, "kb:review:decide");
368
362
  requireConfirmed(args.authorization);
369
363
  if (args.decision !== "approve" && args.decision !== "reject") throw new ManagementToolError("invalid", "decision must be approve or reject");
370
364
  const target = (await host.kbReviewList()).find((item) => item.name === args.name);
@@ -383,7 +377,6 @@ const createManagementTools = (host) => [
383
377
  parameters: {},
384
378
  output: jsonOutput$1,
385
379
  execute: async () => run$2(async () => {
386
- requirePermission$1(await host.context(), "kb:file:extract");
387
380
  return { sync: await host.kbSync() };
388
381
  })
389
382
  }),
@@ -393,7 +386,6 @@ const createManagementTools = (host) => [
393
386
  parameters: {},
394
387
  output: jsonOutput$1,
395
388
  execute: async () => run$2(async () => {
396
- requirePermission$1(await host.context(), "kb:file:extract");
397
389
  return { index: await host.kbIndexRebuild() };
398
390
  })
399
391
  }),
@@ -412,7 +404,6 @@ const createManagementTools = (host) => [
412
404
  },
413
405
  output: jsonOutput$1,
414
406
  execute: async (args) => run$2(async () => {
415
- requirePermission$1(await host.context(), "workflow:read");
416
407
  return { workflows: await host.workflowList(args) };
417
408
  })
418
409
  }),
@@ -449,7 +440,6 @@ const createManagementTools = (host) => [
449
440
  },
450
441
  output: jsonOutput$1,
451
442
  execute: async (args) => run$2(async () => {
452
- requirePermission$1(await host.context(), "workflow:publish");
453
443
  requireConfirmed(args.authorization);
454
444
  return { workflow: await host.workflowUpdate(args) };
455
445
  })
@@ -467,7 +457,6 @@ const createManagementTools = (host) => [
467
457
  },
468
458
  output: jsonOutput$1,
469
459
  execute: async (args) => run$2(async () => {
470
- requirePermission$1(await host.context(), "workflow:manage");
471
460
  requireConfirmed(args.authorization);
472
461
  return { result: await host.workflowUnpublish(args.id) };
473
462
  })
@@ -491,7 +480,6 @@ const createManagementTools = (host) => [
491
480
  },
492
481
  output: jsonOutput$1,
493
482
  execute: async (args) => run$2(async () => {
494
- requirePermission$1(await host.context(), "workflow:review:decide");
495
483
  return { workflows: await host.workflowReviewList(args) };
496
484
  })
497
485
  }),
@@ -505,7 +493,6 @@ const createManagementTools = (host) => [
505
493
  } },
506
494
  output: jsonOutput$1,
507
495
  execute: async (args) => run$2(async () => {
508
- requirePermission$1(await host.context(), "workflow:read");
509
496
  if (!Array.isArray(args.ids) || args.ids.length === 0) throw new ManagementToolError("invalid", "ids is required");
510
497
  return { scan: await host.workflowReviewScan(args.ids) };
511
498
  })
@@ -532,7 +519,6 @@ const createManagementTools = (host) => [
532
519
  },
533
520
  output: jsonOutput$1,
534
521
  execute: async (args) => run$2(async () => {
535
- requirePermission$1(await host.context(), "workflow:review:decide");
536
522
  requireConfirmed(args.authorization);
537
523
  if (args.decision !== "approve" && args.decision !== "reject") throw new ManagementToolError("invalid", "decision must be approve or reject");
538
524
  const ids = Array.isArray(args.ids) ? args.ids.filter((id) => typeof id === "string" && id !== "") : [];
@@ -546,6 +532,355 @@ const createManagementTools = (host) => [
546
532
  })
547
533
  ];
548
534
  //#endregion
535
+ //#region src/toolDisclosure.ts
536
+ /** The four disclosure groups a knowledge-base tool can belong to. */
537
+ const TOOL_GROUPS = [
538
+ "read",
539
+ "write",
540
+ "manage",
541
+ "publish"
542
+ ];
543
+ /**
544
+ * Disclosure rules for every model-facing tool this package registers.
545
+ *
546
+ * The map is the single list of what exists; the coordinator rejects a tool
547
+ * definition without a rule and a rule without a definition at construction time,
548
+ * so a new tool cannot ship undisclosed or leave a stale policy behind.
549
+ */
550
+ const KB_TOOL_ACCESS = Object.freeze({
551
+ kb_search: {
552
+ group: "read",
553
+ permissions: ["kb:file:read"]
554
+ },
555
+ kb_chunk_get: {
556
+ group: "read",
557
+ permissions: ["kb:file:read"]
558
+ },
559
+ kb_outline: {
560
+ group: "read",
561
+ permissions: ["kb:file:read"]
562
+ },
563
+ kb_outline_rewrite: {
564
+ group: "read",
565
+ permissions: ["kb:file:read"]
566
+ },
567
+ kb_vectorize: {
568
+ group: "read",
569
+ permissions: ["kb:file:read"]
570
+ },
571
+ workflow_list: {
572
+ group: "read",
573
+ permissions: ["workflow:read"]
574
+ },
575
+ kb_file_write: {
576
+ group: "write",
577
+ permissions: ["kb:file:write"]
578
+ },
579
+ kb_file_move: {
580
+ group: "write",
581
+ permissions: ["kb:file:write"]
582
+ },
583
+ kb_file_delete: {
584
+ group: "write",
585
+ permissions: ["kb:file:delete"]
586
+ },
587
+ kb_file_mkdir: {
588
+ group: "write",
589
+ permissions: ["kb:file:write"]
590
+ },
591
+ kb_file_extract: {
592
+ group: "write",
593
+ permissions: ["kb:file:extract"]
594
+ },
595
+ kb_outline_save: {
596
+ group: "write",
597
+ permissions: ["kb:file:read", "kb:file:write"]
598
+ },
599
+ kb_config_get: {
600
+ group: "manage",
601
+ permissions: ["kb:config:manage"]
602
+ },
603
+ kb_config_update: {
604
+ group: "manage",
605
+ permissions: ["kb:config:manage"]
606
+ },
607
+ kb_grant_list: {
608
+ group: "manage",
609
+ permissions: ["kb:grant:manage"]
610
+ },
611
+ kb_grant_set: {
612
+ group: "manage",
613
+ permissions: ["kb:grant:manage"]
614
+ },
615
+ kb_audit_list: {
616
+ group: "manage",
617
+ permissions: ["kb:audit:read"]
618
+ },
619
+ kb_review_list: {
620
+ group: "manage",
621
+ permissions: ["kb:review:decide"]
622
+ },
623
+ kb_review_decide: {
624
+ group: "manage",
625
+ permissions: ["kb:review:decide"]
626
+ },
627
+ kb_sync: {
628
+ group: "manage",
629
+ capability: "reviewer"
630
+ },
631
+ kb_index_rebuild: {
632
+ group: "manage",
633
+ permissions: ["kb:config:manage"]
634
+ },
635
+ workflow_unpublish: {
636
+ group: "manage",
637
+ permissions: ["workflow:manage"]
638
+ },
639
+ workflow_review_list: {
640
+ group: "manage",
641
+ permissions: ["workflow:review:decide"]
642
+ },
643
+ workflow_review_scan: {
644
+ group: "manage",
645
+ permissions: ["workflow:review:decide"]
646
+ },
647
+ workflow_review_decide: {
648
+ group: "manage",
649
+ permissions: ["workflow:review:decide"]
650
+ },
651
+ workflow_publish: {
652
+ group: "publish",
653
+ permissions: ["workflow:publish"]
654
+ },
655
+ workflow_update: {
656
+ group: "publish",
657
+ permissions: ["workflow:publish"]
658
+ }
659
+ });
660
+ Object.freeze({
661
+ read: namesIn("read"),
662
+ write: namesIn("write"),
663
+ manage: namesIn("manage"),
664
+ publish: namesIn("publish")
665
+ });
666
+ Object.freeze(Object.keys(KB_TOOL_ACCESS));
667
+ function namesIn(group) {
668
+ return Object.freeze(Object.entries(KB_TOOL_ACCESS).filter(([, rule]) => rule.group === group).map(([name]) => name));
669
+ }
670
+ /**
671
+ * Decide whether one tool is visible for a permission context.
672
+ *
673
+ * An absent context is a denial: disclosure happens before execution, so an
674
+ * unresolved permission tree must withhold the surface rather than expose it.
675
+ * Unknown tool names are denied too; the coordinator rejects them at construction,
676
+ * and this function keeps the same fail-closed answer if one reaches it anyway.
677
+ *
678
+ * @param name - model-facing tool name.
679
+ * @param context - the caller's review context, or undefined while it is unavailable.
680
+ * @returns the decision and the requirements the context did not satisfy.
681
+ */
682
+ function decideToolAccess(name, context) {
683
+ const rule = KB_TOOL_ACCESS[name];
684
+ if (rule === void 0) return {
685
+ allowed: false,
686
+ missing: ["disclosure-policy"]
687
+ };
688
+ if (context === void 0) return {
689
+ allowed: false,
690
+ group: rule.group,
691
+ missing: ["permission-context"]
692
+ };
693
+ const missing = [];
694
+ for (const permission of rule.permissions ?? []) if (!hasPermission(context, permission)) missing.push(permission);
695
+ if (rule.capability === "reviewer" && !(isGlobalAdmin(context) || context.review.canReview === true)) missing.push("reviewer");
696
+ if (rule.capability === "admin" && !isGlobalAdmin(context)) missing.push("admin");
697
+ return {
698
+ allowed: missing.length === 0,
699
+ group: rule.group,
700
+ missing
701
+ };
702
+ }
703
+ /**
704
+ * Register and dispose knowledge-base tools as the caller's permission context changes.
705
+ *
706
+ * The coordinator owns every tool registration. A pass computes one decision per
707
+ * tool, registers the newly allowed ones, disposes the newly denied ones and
708
+ * leaves unchanged tools untouched, so an unchanged context causes no registry
709
+ * churn. Concurrent refreshes share one in-flight context read; a pass that is
710
+ * superseded by a newer context never applies its stale answer.
711
+ *
712
+ * On a context-resolution failure every gated tool is disposed. That is the
713
+ * fail-closed direction: an unavailable permission tree cannot prove a capability,
714
+ * and a tool surface that outlives its proof is exactly the leak disclosure exists
715
+ * to prevent.
716
+ */
717
+ var ToolDisclosureCoordinator = class {
718
+ definitions;
719
+ /** Registered tools and their exact registry disposers. */
720
+ disposers = /* @__PURE__ */ new Map();
721
+ options;
722
+ /** Monotonic pass counter; a slower older read must not overwrite a newer one. */
723
+ generation = 0;
724
+ inFlight;
725
+ disposed = false;
726
+ snapshot;
727
+ constructor(options) {
728
+ this.options = options;
729
+ this.definitions = new Map(options.tools.map((definition) => [definition.name, definition]));
730
+ assertCompleteDisclosure(this.definitions.keys());
731
+ }
732
+ /** Tool names currently registered, in definition order. */
733
+ get registeredTools() {
734
+ return [...this.definitions.keys()].filter((name) => this.disposers.has(name));
735
+ }
736
+ /** The most recent disclosure pass, if one has completed. */
737
+ get lastSnapshot() {
738
+ return this.snapshot;
739
+ }
740
+ /**
741
+ * Recompute the tool surface.
742
+ *
743
+ * @param context - a context already read by the caller (a route gate or status
744
+ * request). Passing it avoids a second backend round trip. Omit it to resolve
745
+ * one through {@link ToolDisclosureOptions.resolveContext}.
746
+ * @returns the resulting snapshot; a context failure is reported in `error`
747
+ * rather than thrown, because a refresh must never fail the request that
748
+ * triggered it.
749
+ */
750
+ syncToolDisclosure(context) {
751
+ if (this.disposed) return Promise.resolve(this.emptySnapshot("tool disclosure is disposed"));
752
+ if (context !== void 0) {
753
+ this.generation += 1;
754
+ try {
755
+ return Promise.resolve(this.apply(context));
756
+ } catch (error) {
757
+ return Promise.resolve(this.fail(error));
758
+ }
759
+ }
760
+ if (this.inFlight !== void 0) return this.inFlight;
761
+ const generation = ++this.generation;
762
+ const run = Promise.resolve().then(() => this.options.resolveContext()).then((resolved) => generation === this.generation ? this.apply(resolved) : this.snapshot ?? this.emptySnapshot("a newer disclosure pass superseded this one")).catch((error) => generation === this.generation ? this.fail(error) : this.snapshot ?? this.emptySnapshot("a newer disclosure pass superseded this one")).finally(() => {
763
+ if (this.inFlight === run) this.inFlight = void 0;
764
+ });
765
+ this.inFlight = run;
766
+ return run;
767
+ }
768
+ /** Dispose every tool this coordinator registered; later refreshes are no-ops. */
769
+ dispose() {
770
+ this.disposed = true;
771
+ this.disposeRegistered();
772
+ }
773
+ /** Register allowed tools and dispose denied ones for one resolved context. */
774
+ apply(context) {
775
+ const denied = [];
776
+ for (const [name, definition] of this.definitions) {
777
+ const decision = decideToolAccess(name, context);
778
+ if (decision.allowed) {
779
+ if (!this.disposers.has(name)) try {
780
+ this.disposers.set(name, this.options.registry.register(definition));
781
+ } catch (error) {
782
+ this.options.log?.(`tool disclosure: could not register ${name}: ${messageOf$1(error)}`);
783
+ denied.push({
784
+ name,
785
+ ...decision.group === void 0 ? {} : { group: decision.group },
786
+ missing: ["register-failed"]
787
+ });
788
+ }
789
+ continue;
790
+ }
791
+ denied.push({
792
+ name,
793
+ ...decision.group === void 0 ? {} : { group: decision.group },
794
+ missing: decision.missing
795
+ });
796
+ const dispose = this.disposers.get(name);
797
+ if (dispose !== void 0) {
798
+ try {
799
+ dispose();
800
+ } catch (error) {
801
+ this.options.log?.(`tool disclosure: could not dispose ${name}: ${messageOf$1(error)}`);
802
+ }
803
+ this.disposers.delete(name);
804
+ }
805
+ }
806
+ const registered = this.registeredTools;
807
+ const snapshot = {
808
+ registered,
809
+ denied,
810
+ activeGroups: TOOL_GROUPS.filter((group) => registered.some((name) => KB_TOOL_ACCESS[name]?.group === group))
811
+ };
812
+ const changed = this.snapshot === void 0 || this.snapshot.registered.join("\0") !== registered.join("\0") || this.snapshot.error === void 0 !== (snapshot.error === void 0);
813
+ this.snapshot = snapshot;
814
+ if (changed) this.options.log?.(`tool disclosure: registered ${String(registered.length)} tool(s) [${registered.join(", ")}]; withheld ${String(denied.length)}`);
815
+ return snapshot;
816
+ }
817
+ /** Dispose everything and report why the surface is empty. */
818
+ fail(error) {
819
+ this.disposeRegistered();
820
+ const message = messageOf$1(error);
821
+ const snapshot = {
822
+ registered: [],
823
+ denied: [...this.definitions.keys()].map((name) => ({
824
+ name,
825
+ ...KB_TOOL_ACCESS[name] === void 0 ? {} : { group: KB_TOOL_ACCESS[name].group },
826
+ missing: ["permission-context"]
827
+ })),
828
+ activeGroups: [],
829
+ error: message
830
+ };
831
+ this.snapshot = snapshot;
832
+ this.options.log?.(`tool disclosure: permission context unavailable; withheld every gated tool: ${message}`);
833
+ return snapshot;
834
+ }
835
+ /** Dispose every registered tool; safe to call more than once. */
836
+ disposeRegistered() {
837
+ for (const dispose of this.disposers.values()) try {
838
+ dispose();
839
+ } catch (error) {
840
+ this.options.log?.(`tool disclosure: could not dispose a tool: ${messageOf$1(error)}`);
841
+ }
842
+ this.disposers.clear();
843
+ }
844
+ /** Snapshot for a pass that cannot run at all. */
845
+ emptySnapshot(reason) {
846
+ return {
847
+ registered: [],
848
+ denied: [...this.definitions.keys()].map((name) => ({
849
+ name,
850
+ ...KB_TOOL_ACCESS[name] === void 0 ? {} : { group: KB_TOOL_ACCESS[name].group },
851
+ missing: ["permission-context"]
852
+ })),
853
+ activeGroups: [],
854
+ error: reason
855
+ };
856
+ }
857
+ };
858
+ /**
859
+ * Build a coordinator.
860
+ *
861
+ * @param options - the definitions, registry, context resolver and log sink.
862
+ * @returns the coordinator the plugin stores and refreshes.
863
+ */
864
+ function createToolDisclosure(options) {
865
+ return new ToolDisclosureCoordinator(options);
866
+ }
867
+ /**
868
+ * Reject a policy map that drifted from the tool set.
869
+ *
870
+ * Called by the constructor, so a missing rule or a stale rule fails at plugin
871
+ * start rather than silently exposing or hiding a tool later.
872
+ */
873
+ function assertCompleteDisclosure(names) {
874
+ const present = new Set(names);
875
+ const unknown = [...present].filter((name) => KB_TOOL_ACCESS[name] === void 0);
876
+ const stale = Object.keys(KB_TOOL_ACCESS).filter((name) => !present.has(name));
877
+ if (unknown.length > 0 || stale.length > 0) throw new Error("knowledge-base tool disclosure is incomplete:" + (unknown.length === 0 ? "" : ` no policy for ${unknown.join(", ")};`) + (stale.length === 0 ? "" : ` policy without a tool for ${stale.join(", ")};`));
878
+ }
879
+ /** Error text safe to log from a contained refresh. */
880
+ function messageOf$1(error) {
881
+ return error instanceof Error ? error.message : String(error);
882
+ }
883
+ //#endregion
549
884
  //#region src/workflowPublish.ts
550
885
  /**
551
886
  * The workflow-plaza publish store.
@@ -8244,16 +8579,12 @@ const run = async (path, operation) => {
8244
8579
  throw asKnowledgeToolError(error, path);
8245
8580
  }
8246
8581
  };
8247
- /** Require one backend DSH permission before the tool touches the tree. */
8248
- async function requirePermission(host, permission) {
8249
- if (host.hasPermission === void 0) return;
8250
- if (!await host.hasPermission(permission)) throw new KnowledgeToolError("forbidden", `缺少权限 ${permission}`);
8251
- }
8252
8582
  /**
8253
- * Build the five file model-facing tools.
8583
+ * Build the knowledge-base model-facing tools.
8254
8584
  *
8255
- * Returned definitions are registered by `apply`; tests call the same definitions with
8256
- * a fake host to assert permission, path and audit behaviour without a live backend.
8585
+ * Returned definitions are registered by `apply` through the permission-based
8586
+ * disclosure coordinator; tests call the same definitions with a fake host to assert
8587
+ * path, audit and result behaviour without a live backend or a permission backend.
8257
8588
  */
8258
8589
  const createKnowledgeFileTools = (host) => [
8259
8590
  defineTool({
@@ -8279,7 +8610,6 @@ const createKnowledgeFileTools = (host) => [
8279
8610
  execute: async (args) => run(args.path, async () => {
8280
8611
  const relPath = assertToolPath(args.path);
8281
8612
  const content = typeof args.content === "string" ? args.content : "";
8282
- await requirePermission(host, "kb:file:write");
8283
8613
  await requireWrite(host, relPath);
8284
8614
  const caller = await host.resolveCaller();
8285
8615
  const existing = await host.statEntry(relPath);
@@ -8344,7 +8674,6 @@ const createKnowledgeFileTools = (host) => [
8344
8674
  execute: async (args) => run(args.from, async () => {
8345
8675
  const from = assertToolPath(args.from);
8346
8676
  const to = assertToolPath(args.to, true);
8347
- await requirePermission(host, "kb:file:write");
8348
8677
  await requireWrite(host, from);
8349
8678
  const caller = await host.resolveCaller();
8350
8679
  const targetEntry = to === "" ? void 0 : await host.statEntry(to);
@@ -8391,7 +8720,6 @@ const createKnowledgeFileTools = (host) => [
8391
8720
  output: jsonOutput,
8392
8721
  isConcurrencySafe: () => true,
8393
8722
  execute: async (args) => run(void 0, async () => {
8394
- await requirePermission(host, "kb:file:read");
8395
8723
  if (host.searchIndex === void 0) throw new KnowledgeToolError("not-configured", "knowledge index is unavailable");
8396
8724
  const query = typeof args.query === "string" ? args.query.trim() : "";
8397
8725
  if (query === "") throw new KnowledgeToolError("empty-path", "query must be non-empty");
@@ -8482,7 +8810,6 @@ const createKnowledgeFileTools = (host) => [
8482
8810
  },
8483
8811
  output: jsonOutput,
8484
8812
  execute: async (args) => run(args.path, async () => {
8485
- await requirePermission(host, "kb:file:read");
8486
8813
  if (host.vectorize === void 0) throw new KnowledgeToolError("not-configured", "vector indexing is unavailable");
8487
8814
  const hasDocId = args.docId !== void 0;
8488
8815
  const hasPath = typeof args.path === "string" && args.path.trim() !== "";
@@ -8529,7 +8856,6 @@ const createKnowledgeFileTools = (host) => [
8529
8856
  output: jsonOutput,
8530
8857
  isConcurrencySafe: () => true,
8531
8858
  execute: async (args) => run(void 0, async () => {
8532
- await requirePermission(host, "kb:file:read");
8533
8859
  if (host.chunkGet === void 0) throw new KnowledgeToolError("not-configured", "knowledge chunk resolver is unavailable");
8534
8860
  const fromToken = typeof args.citation === "string" ? /^kb:\/\/doc\/([^/\s?#]+)\/chunk\/([^/\s?#]+)/i.exec(args.citation.trim()) : null;
8535
8861
  const docId = Number(fromToken?.[1] ?? args.docId);
@@ -8570,7 +8896,6 @@ const createKnowledgeFileTools = (host) => [
8570
8896
  output: jsonOutput,
8571
8897
  isConcurrencySafe: () => true,
8572
8898
  execute: async (args) => run(void 0, async () => {
8573
- await requirePermission(host, "kb:file:read");
8574
8899
  if (host.outline === void 0) throw new KnowledgeToolError("not-configured", "knowledge outline resolver is unavailable");
8575
8900
  const rawToken = args.citation ?? args.citationToken ?? "";
8576
8901
  const fromToken = rawToken.trim() !== "" ? /^kb:\/\/doc\/([^/\s?#]+)(?:\/chunk\/[^/\s?#]+)?/iu.exec(rawToken.trim()) : null;
@@ -8594,7 +8919,6 @@ const createKnowledgeFileTools = (host) => [
8594
8919
  output: jsonOutput,
8595
8920
  isConcurrencySafe: () => false,
8596
8921
  execute: async (args) => run(void 0, async () => {
8597
- await requirePermission(host, "kb:file:read");
8598
8922
  if (host.outlineRewrite === void 0) throw new KnowledgeToolError("not-configured", "knowledge outline rewrite is unavailable");
8599
8923
  const docId = Number(args.docId);
8600
8924
  if (!Number.isInteger(docId) || docId <= 0) throw new KnowledgeToolError("invalid-citation", "docId must be a positive integer");
@@ -8677,8 +9001,6 @@ const createKnowledgeFileTools = (host) => [
8677
9001
  },
8678
9002
  output: jsonOutput,
8679
9003
  execute: async (args) => run(void 0, async () => {
8680
- await requirePermission(host, "kb:file:read");
8681
- await requirePermission(host, "kb:file:write");
8682
9004
  if (host.outlineSave === void 0) throw new KnowledgeToolError("not-configured", "knowledge outline saver is unavailable");
8683
9005
  const docId = Number(args.docId);
8684
9006
  if (!Number.isInteger(docId) || docId <= 0) throw new KnowledgeToolError("invalid-summary", "docId must be a positive integer");
@@ -8708,7 +9030,6 @@ const createKnowledgeFileTools = (host) => [
8708
9030
  output: jsonOutput,
8709
9031
  execute: async (args) => run(args.path, async () => {
8710
9032
  const relPath = assertToolPath(args.path);
8711
- await requirePermission(host, "kb:file:delete");
8712
9033
  await requireWrite(host, relPath);
8713
9034
  const caller = await host.resolveCaller();
8714
9035
  const entry = await host.statEntry(relPath);
@@ -8737,7 +9058,6 @@ const createKnowledgeFileTools = (host) => [
8737
9058
  output: jsonOutput,
8738
9059
  execute: async (args) => run(args.path, async () => {
8739
9060
  const relPath = assertToolPath(args.path);
8740
- await requirePermission(host, "kb:file:write");
8741
9061
  await requireWrite(host, relPath);
8742
9062
  const caller = await host.resolveCaller();
8743
9063
  const created = await host.createEntry(relPath, { directory: true });
@@ -8762,7 +9082,6 @@ const createKnowledgeFileTools = (host) => [
8762
9082
  output: jsonOutput,
8763
9083
  execute: async (args) => run(args.path, async () => {
8764
9084
  const relPath = assertToolPath(args.path ?? "", true);
8765
- await requirePermission(host, "kb:file:extract");
8766
9085
  const caller = await host.resolveCaller();
8767
9086
  if (relPath === "") {
8768
9087
  const candidates = await host.listExtractCandidates(20);
@@ -8989,6 +9308,54 @@ function isTeamDeployment(config) {
8989
9308
  */
8990
9309
  const LOCAL_USER_ID = "local";
8991
9310
  /**
9311
+ * The permission context a personal deployment discloses tools from.
9312
+ *
9313
+ * A personal deployment has no accounts, so there is nobody to ask: the local
9314
+ * operator owns every capability the product knows about. Kept as a function rather
9315
+ * than the inline object inside the management host so the disclosure coordinator
9316
+ * can register the personal tool surface synchronously at startup, instead of
9317
+ * waiting for a backend round trip that does not exist.
9318
+ *
9319
+ * @returns the full-capability context a local owner holds.
9320
+ */
9321
+ function localManagementContext() {
9322
+ return {
9323
+ userId: "local",
9324
+ username: "local",
9325
+ displayName: "个人用户",
9326
+ primaryRole: "LOCAL_OWNER",
9327
+ roleCodes: ["LOCAL_OWNER"],
9328
+ permissions: [
9329
+ "kb:file:read",
9330
+ "kb:file:write",
9331
+ "kb:file:delete",
9332
+ "kb:file:extract",
9333
+ "kb:audit:read",
9334
+ "kb:grant:manage",
9335
+ "kb:review:decide",
9336
+ "kb:config:manage",
9337
+ "workflow:read",
9338
+ "workflow:publish",
9339
+ "workflow:manage",
9340
+ "workflow:review:decide",
9341
+ "workflow:review:global"
9342
+ ],
9343
+ departmentCode: null,
9344
+ departmentPathCodes: [],
9345
+ dataScope: {
9346
+ all: true,
9347
+ self: true,
9348
+ departmentCodes: []
9349
+ },
9350
+ review: {
9351
+ canReview: true,
9352
+ canReviewGlobal: true,
9353
+ canManagePermissions: true,
9354
+ departments: []
9355
+ }
9356
+ };
9357
+ }
9358
+ /**
8992
9359
  * Whether a personal deployment may read or write one path.
8993
9360
  *
8994
9361
  * A personal deployment has no OTHER users to protect a path from, so there is no grant to
@@ -10837,6 +11204,24 @@ function knowledgeBaseSkillProvider() {
10837
11204
  }
10838
11205
  };
10839
11206
  }
11207
+ /**
11208
+ * Per-plugin-instance disclosure coordinators, keyed by the Host context.
11209
+ *
11210
+ * `apply` is synchronous and the Host does not hand a plugin a return value, but
11211
+ * the coordinator must be reachable for two legitimate callers: the host integration
11212
+ * that wants to force a refresh, and the tests that drive `apply` directly. A
11213
+ * WeakMap keeps this a discovery seam rather than a second lifecycle owner.
11214
+ */
11215
+ const toolDisclosures = /* @__PURE__ */ new WeakMap();
11216
+ /**
11217
+ * The permission-based tool disclosure coordinator this plugin instance registered.
11218
+ *
11219
+ * @param host - the Host context the plugin was applied to.
11220
+ * @returns the coordinator, or undefined when this context has no knowledge-base plugin.
11221
+ */
11222
+ function knowledgeBaseToolDisclosure(host) {
11223
+ return toolDisclosures.get(host);
11224
+ }
10840
11225
  function apply(ctx, deploymentConfig) {
10841
11226
  const config = deploymentConfig;
10842
11227
  const host = ctx;
@@ -11037,6 +11422,15 @@ function apply(ctx, deploymentConfig) {
11037
11422
  ...lastFailure === void 0 ? {} : { failure: lastFailure }
11038
11423
  };
11039
11424
  };
11425
+ /**
11426
+ * Permission-based tool disclosure, assigned once the host seams exist below.
11427
+ *
11428
+ * Declared before the HTTP surface because the route handler closes over it: the
11429
+ * route gate is one of the two existing places this plugin already reads the
11430
+ * caller's permission context, so it is also where a changed context refreshes the
11431
+ * model-facing tool surface.
11432
+ */
11433
+ let toolDisclosure;
11040
11434
  ctx.effect(() => host.webServer.register({
11041
11435
  kind: "prefix",
11042
11436
  path: "/knowledge-base/api",
@@ -11049,6 +11443,7 @@ function apply(ctx, deploymentConfig) {
11049
11443
  const permissionContext = await Promise.race([managementHost.context().catch(() => void 0), new Promise((resolve) => setTimeout(() => {
11050
11444
  resolve(void 0);
11051
11445
  }, 750))]);
11446
+ if (permissionContext !== void 0) await toolDisclosure?.syncToolDisclosure(permissionContext);
11052
11447
  if (permissionContext !== void 0 && permissionContext.permissions.length > 0 && !hasPermission(permissionContext, requiredPermission)) return writeJson(res, 403, {
11053
11448
  error: `缺少权限 ${requiredPermission}`,
11054
11449
  code: "forbidden",
@@ -11257,21 +11652,18 @@ function apply(ctx, deploymentConfig) {
11257
11652
  if (!initializer.hasSucceeded()) await initializer.ensure();
11258
11653
  const canManage = isTeamDeployment(config) && await canManageGrants(host, config);
11259
11654
  let permissionKeys = [];
11655
+ let permissionContext;
11260
11656
  if (isTeamDeployment(config)) try {
11261
- permissionKeys = [...(await managementHost.context()).permissions];
11657
+ permissionContext = await managementHost.context();
11658
+ permissionKeys = [...permissionContext.permissions];
11262
11659
  } catch {
11263
11660
  permissionKeys = [];
11264
11661
  }
11265
- else permissionKeys = [
11266
- "kb:file:read",
11267
- "kb:file:write",
11268
- "kb:file:delete",
11269
- "kb:file:extract",
11270
- "kb:audit:read",
11271
- "kb:grant:manage",
11272
- "kb:review:decide",
11273
- "kb:config:manage"
11274
- ];
11662
+ else {
11663
+ permissionContext = localManagementContext();
11664
+ permissionKeys = [...permissionContext.permissions];
11665
+ }
11666
+ if (permissionContext !== void 0) await toolDisclosure?.syncToolDisclosure(permissionContext);
11275
11667
  if (config.storageMode === "local") {
11276
11668
  const localOnly = await localStatus(config);
11277
11669
  let localCaller;
@@ -12679,7 +13071,7 @@ function apply(ctx, deploymentConfig) {
12679
13071
  if (typeof hostWithInject.inject === "function") hostWithInject.inject(["skills"], (scoped) => {
12680
13072
  scoped.skills.registerProvider(() => knowledgeBaseSkillProvider());
12681
13073
  });
12682
- ctx.effect(() => host.tools.register(defineTool({
13074
+ const workflowPublishTool = defineTool({
12683
13075
  name: "workflow_publish",
12684
13076
  description: "Publish one workflow from this conversation to the Workflow Plaza. The record is created as PENDING; it does NOT appear in the public plaza until an administrator approves it. 发布后需管理员审核,通过后才出现在广场。Use this only when the user explicitly asks to publish/share the workflow. Do not use it to edit an existing workflow or to run a task now. Provide the standing instruction that will run on the chosen schedule, in the user's language. cronHint should be a 5-field cron expression when the user gave one; category is a short group label.",
12685
13077
  parameters: {
@@ -12746,8 +13138,7 @@ function apply(ctx, deploymentConfig) {
12746
13138
  execute: async (args, exec) => {
12747
13139
  const actor = await resolveWorkflowActor(host, config);
12748
13140
  const context = await managementHost.context();
12749
- requirePermission$1(context, "workflow:publish", "workflow_publish");
12750
- if (args.visibility === "global") requirePermission$1(context, "workflow:review:global", "global workflow publish");
13141
+ if (args.visibility === "global" && !hasPermission(context, "workflow:review:global")) throw new ManagementToolError("forbidden", "缺少权限 workflow:review:global(global workflow publish)");
12751
13142
  const scope = normalizePublishScope(context, args.visibility, args.departmentCode);
12752
13143
  const sessionId = exec.agent?.session.id;
12753
13144
  const result = await publishWorkflow(publishStorePath(publishDirOf(config.publishDir)), {
@@ -12763,7 +13154,7 @@ function apply(ctx, deploymentConfig) {
12763
13154
  report: `${prefix} 管理员审核通过后才会出现在工作流广场;当前状态:待审核。`
12764
13155
  };
12765
13156
  }
12766
- })), "dsh-knowledge-base: workflow_publish tool");
13157
+ });
12767
13158
  /** Sidecar path, guarded against living inside the agent-visible tree. */
12768
13159
  const knowledgeIndexPath = () => {
12769
13160
  const plan = localConfig(config);
@@ -13111,14 +13502,6 @@ function apply(ctx, deploymentConfig) {
13111
13502
  }
13112
13503
  };
13113
13504
  const knowledgeToolHost = {
13114
- hasPermission: async (permission) => {
13115
- if (!isTeamDeployment(config)) return true;
13116
- try {
13117
- return (await managementHost.context()).permissions.includes(permission);
13118
- } catch {
13119
- return true;
13120
- }
13121
- },
13122
13505
  vectorize: async (input) => {
13123
13506
  try {
13124
13507
  await syncKnowledgeIndex(false, false);
@@ -13459,8 +13842,9 @@ function apply(ctx, deploymentConfig) {
13459
13842
  * Management-tool host.
13460
13843
  *
13461
13844
  * Every method reads/writes through the same backend or host primitive the matching
13462
- * UI surface uses. Role checks happen in the tool layer (which has the review
13463
- * context); this object does not silently turn a denial into an empty result.
13845
+ * UI surface uses. Role checks happen once at tool disclosure; the context reads
13846
+ * that remain here feed data scoping and audit identity, and the backend still
13847
+ * refuses an operation the caller may not perform.
13464
13848
  */
13465
13849
  const builtInWorkflowRows = [
13466
13850
  {
@@ -13476,42 +13860,18 @@ function apply(ctx, deploymentConfig) {
13476
13860
  title: "Archive stale personal documents"
13477
13861
  }
13478
13862
  ];
13863
+ /**
13864
+ * The acting user id recorded in a management audit line.
13865
+ *
13866
+ * This is identity, not authorization: the permission decision happened at tool
13867
+ * disclosure, and the backend re-checks the write. Reading `/auth/me` instead of
13868
+ * the full review context keeps an audit write from fetching a permission tree it
13869
+ * does not consult.
13870
+ */
13871
+ const managementActorId = async () => isTeamDeployment(config) ? (await fetchCaller(host, config)).userId : LOCAL_USER_ID;
13479
13872
  const managementHost = {
13480
13873
  context: async () => {
13481
- if (!isTeamDeployment(config)) return {
13482
- userId: "local",
13483
- username: "local",
13484
- displayName: "个人用户",
13485
- primaryRole: "LOCAL_OWNER",
13486
- roleCodes: ["LOCAL_OWNER"],
13487
- permissions: [
13488
- "kb:file:read",
13489
- "kb:file:write",
13490
- "kb:file:delete",
13491
- "kb:file:extract",
13492
- "kb:audit:read",
13493
- "kb:grant:manage",
13494
- "kb:review:decide",
13495
- "workflow:read",
13496
- "workflow:publish",
13497
- "workflow:manage",
13498
- "workflow:review:decide",
13499
- "workflow:review:global"
13500
- ],
13501
- departmentCode: null,
13502
- departmentPathCodes: [],
13503
- dataScope: {
13504
- all: true,
13505
- self: true,
13506
- departmentCodes: []
13507
- },
13508
- review: {
13509
- canReview: true,
13510
- canReviewGlobal: true,
13511
- canManagePermissions: true,
13512
- departments: []
13513
- }
13514
- };
13874
+ if (!isTeamDeployment(config)) return localManagementContext();
13515
13875
  const raw = await request(host, config, "/api/ai/review/context");
13516
13876
  const dataScope = raw.dataScope ?? {};
13517
13877
  const review = raw.review ?? {};
@@ -13549,7 +13909,7 @@ function apply(ctx, deploymentConfig) {
13549
13909
  },
13550
13910
  kbConfigGet: async () => {
13551
13911
  const plan = localConfig(config);
13552
- const actorId = isTeamDeployment(config) ? (await fetchCaller(host, config)).userId : "local";
13912
+ const actorId = await managementActorId();
13553
13913
  const viewDir = viewPathFor({
13554
13914
  actualRoot: plan.actualRoot,
13555
13915
  viewRoot: plan.viewRoot,
@@ -13584,7 +13944,7 @@ function apply(ctx, deploymentConfig) {
13584
13944
  if (patch.reviewRequired !== void 0) {
13585
13945
  if (typeof patch.reviewRequired !== "boolean") throw new ManagementToolError("invalid", "reviewRequired must be boolean");
13586
13946
  const plan = localConfig(config);
13587
- const actorId = isTeamDeployment(config) ? (await fetchCaller(host, config)).userId : "local";
13947
+ const actorId = await managementActorId();
13588
13948
  const targetView = viewPathFor({
13589
13949
  actualRoot: plan.actualRoot,
13590
13950
  viewRoot: plan.viewRoot,
@@ -13611,7 +13971,7 @@ function apply(ctx, deploymentConfig) {
13611
13971
  });
13612
13972
  await appendManagementAudit(config, host, {
13613
13973
  action: "kb_grant_set",
13614
- actor: (await managementHost.context()).userId,
13974
+ actor: await managementActorId(),
13615
13975
  detail: input
13616
13976
  });
13617
13977
  return created;
@@ -13645,7 +14005,7 @@ function apply(ctx, deploymentConfig) {
13645
14005
  await discardPendingWrite(plan.actualRoot, input.name);
13646
14006
  await appendManagementAudit(config, host, {
13647
14007
  action: "kb_review_reject",
13648
- actor: (await managementHost.context()).userId,
14008
+ actor: await managementActorId(),
13649
14009
  detail: {
13650
14010
  name: input.name,
13651
14011
  relPath: submission.relPath,
@@ -13664,7 +14024,7 @@ function apply(ctx, deploymentConfig) {
13664
14024
  await refreshViewIfStale(true, created.relPath);
13665
14025
  await appendManagementAudit(config, host, {
13666
14026
  action: "kb_review_approve",
13667
- actor: (await managementHost.context()).userId,
14027
+ actor: await managementActorId(),
13668
14028
  detail: {
13669
14029
  name: input.name,
13670
14030
  relPath: submission.relPath,
@@ -13755,7 +14115,7 @@ function apply(ctx, deploymentConfig) {
13755
14115
  });
13756
14116
  await appendManagementAudit(config, host, {
13757
14117
  action: "workflow_update",
13758
- actor: (await managementHost.context()).userId,
14118
+ actor: await managementActorId(),
13759
14119
  detail: { id }
13760
14120
  });
13761
14121
  return updated;
@@ -13765,7 +14125,7 @@ function apply(ctx, deploymentConfig) {
13765
14125
  const result = await request(host, config, "/api/ai/workflow-publications/" + encodeURIComponent(id), { method: "DELETE" });
13766
14126
  await appendManagementAudit(config, host, {
13767
14127
  action: "workflow_unpublish",
13768
- actor: (await managementHost.context()).userId,
14128
+ actor: await managementActorId(),
13769
14129
  detail: { id }
13770
14130
  });
13771
14131
  return result;
@@ -13799,8 +14159,30 @@ function apply(ctx, deploymentConfig) {
13799
14159
  });
13800
14160
  }
13801
14161
  };
13802
- for (const definition of createKnowledgeFileTools(knowledgeToolHost)) ctx.effect(() => host.tools.register(definition), "dsh-knowledge-base: " + definition.name + " tool");
13803
- for (const definition of createManagementTools(managementHost)) ctx.effect(() => host.tools.register(definition), "dsh-knowledge-base: " + definition.name + " tool");
14162
+ const disclosure = createToolDisclosure({
14163
+ tools: [
14164
+ workflowPublishTool,
14165
+ ...createKnowledgeFileTools(knowledgeToolHost),
14166
+ ...createManagementTools(managementHost)
14167
+ ],
14168
+ registry: { register: (definition) => host.tools.register(definition) },
14169
+ resolveContext: () => managementHost.context(),
14170
+ log: (message) => host.logger?.warn?.("knowledge-base: " + message)
14171
+ });
14172
+ toolDisclosures.set(ctx, disclosure);
14173
+ toolDisclosure = disclosure;
14174
+ ctx.effect(() => () => disclosure.dispose(), "dsh-knowledge-base: permission-based tool disclosure");
14175
+ if (isTeamDeployment(config)) disclosure.syncToolDisclosure();
14176
+ else disclosure.syncToolDisclosure(localManagementContext());
14177
+ const hostWithEvents = host;
14178
+ if (typeof hostWithEvents.on === "function") ctx.effect(() => {
14179
+ const off = hostWithEvents.on?.("credentials/reference-updated", (ref) => {
14180
+ if (ref === config.credentialRef) disclosure.syncToolDisclosure();
14181
+ });
14182
+ return () => {
14183
+ off?.();
14184
+ };
14185
+ }, "dsh-knowledge-base: tool disclosure refresh on credential change");
13804
14186
  ctx.effect(() => startExternalChangeWatcher({
13805
14187
  actualRoot: localConfig(config).actualRoot,
13806
14188
  log: (message) => host.logger?.warn?.("knowledge-base: " + message),
@@ -13844,4 +14226,4 @@ async function executeStep(runner, step, hostId, record) {
13844
14226
  }
13845
14227
  }
13846
14228
  //#endregion
13847
- export { Config, KB_ROOT_RESOURCE_ID, KB_SUMMARIZE_SKILL_DESCRIPTION, KB_SUMMARIZE_SKILL_NAME, KB_SUMMARIZE_SKILL_RANK, KB_VECTORIZE_SKILL_DESCRIPTION, KB_VECTORIZE_SKILL_NAME, KB_VECTORIZE_SKILL_RANK, KNOWLEDGE_SKILL_DESCRIPTION, KNOWLEDGE_SKILL_INVOCATION, KNOWLEDGE_SKILL_NAME, KNOWLEDGE_SKILL_PROVIDER, KNOWLEDGE_SKILL_RANK, KnowledgeError, KnowledgeWriteError, LOCAL_USER_ID, MAX_KNOWLEDGE_WRITE_BYTES, TEMPLATE_READ_TIMEOUT_MS, actualRelPathForView, apply, canManageGrants, canReadPath, canReviewWorkflows, canWritePath, checkLocalPath, classifyMount, createGrant, createKnowledgeEntry, extractBeside, extractionOutcomeOf, fetchCaller, fetchGrants, fetchPrincipals, inject, isTeamDeployment, kbSummarizeSkillPath, kbVectorizeSkillPath, knowledgeAncestorsFor, knowledgeBaseSkillProvider, knowledgeSkillPath, listDirectory, listDirectoryShapes, listDirectoryWithPermissions, listRelativeFiles, localConfig, localStatus, localStorageRoot, mineruUrlFor, name, permissionForKbRoute, planContext, readAuthorized, readDepartmentOf, readKnowledgeFile, readKnowledgeFileForDownload, readSharedMineruUrl, resolveMineruUrl, resolveWorkflowActor, revokeGrant, searchKnowledge, syncCallerView, tutorialTemplatePath, withLocalStorageRoot, withStoredStorage };
14229
+ export { Config, KB_ROOT_RESOURCE_ID, KB_SUMMARIZE_SKILL_DESCRIPTION, KB_SUMMARIZE_SKILL_NAME, KB_SUMMARIZE_SKILL_RANK, KB_VECTORIZE_SKILL_DESCRIPTION, KB_VECTORIZE_SKILL_NAME, KB_VECTORIZE_SKILL_RANK, KNOWLEDGE_SKILL_DESCRIPTION, KNOWLEDGE_SKILL_INVOCATION, KNOWLEDGE_SKILL_NAME, KNOWLEDGE_SKILL_PROVIDER, KNOWLEDGE_SKILL_RANK, KnowledgeError, KnowledgeWriteError, LOCAL_USER_ID, MAX_KNOWLEDGE_WRITE_BYTES, TEMPLATE_READ_TIMEOUT_MS, actualRelPathForView, apply, canManageGrants, canReadPath, canReviewWorkflows, canWritePath, checkLocalPath, classifyMount, createGrant, createKnowledgeEntry, extractBeside, extractionOutcomeOf, fetchCaller, fetchGrants, fetchPrincipals, inject, isTeamDeployment, kbSummarizeSkillPath, kbVectorizeSkillPath, knowledgeAncestorsFor, knowledgeBaseSkillProvider, knowledgeBaseToolDisclosure, knowledgeSkillPath, listDirectory, listDirectoryShapes, listDirectoryWithPermissions, listRelativeFiles, localConfig, localManagementContext, localStatus, localStorageRoot, mineruUrlFor, name, permissionForKbRoute, planContext, readAuthorized, readDepartmentOf, readKnowledgeFile, readKnowledgeFileForDownload, readSharedMineruUrl, resolveMineruUrl, resolveWorkflowActor, revokeGrant, searchKnowledge, syncCallerView, tutorialTemplatePath, withLocalStorageRoot, withStoredStorage };