Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c2897ba68b | |||
| 9ff933ba98 |
@@ -1,7 +1,12 @@
|
|||||||
var __defProp = Object.defineProperty;
|
var __defProp = Object.defineProperty;
|
||||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||||
var __esm = (fn, res) => function __init() {
|
var __esm = (fn, res, err2) => function __init() {
|
||||||
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
if (err2) throw err2[0];
|
||||||
|
try {
|
||||||
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
||||||
|
} catch (e) {
|
||||||
|
throw err2 = [e], e;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
var __export = (target, all) => {
|
var __export = (target, all) => {
|
||||||
for (var name in all)
|
for (var name in all)
|
||||||
@@ -102,7 +107,7 @@ function applyBaseRuntimeOptions(runtime, options) {
|
|||||||
function applyModuleEvalRuntimeOptions(runtime, options) {
|
function applyModuleEvalRuntimeOptions(runtime, options) {
|
||||||
options.moduleLoader && runtime.setModuleLoader(options.moduleLoader), options.shouldInterrupt && runtime.setInterruptHandler(options.shouldInterrupt), options.memoryLimitBytes !== void 0 && runtime.setMemoryLimit(options.memoryLimitBytes), options.maxStackSizeBytes !== void 0 && runtime.setMaxStackSize(options.maxStackSizeBytes);
|
options.moduleLoader && runtime.setModuleLoader(options.moduleLoader), options.shouldInterrupt && runtime.setInterruptHandler(options.shouldInterrupt), options.memoryLimitBytes !== void 0 && runtime.setMemoryLimit(options.memoryLimitBytes), options.maxStackSizeBytes !== void 0 && runtime.setMaxStackSize(options.maxStackSizeBytes);
|
||||||
}
|
}
|
||||||
var __defProp2, __export2, QTS_DEBUG, errors_exports, QuickJSUnwrapError, QuickJSWrongOwner, QuickJSUseAfterFree, QuickJSNotImplemented, QuickJSAsyncifyError, QuickJSAsyncifySuspended, QuickJSMemoryLeakDetected, QuickJSEmscriptenModuleError, QuickJSUnknownIntrinsic, QuickJSPromisePending, QuickJSEmptyGetOwnPropertyNames, AwaitYield, UsingDisposable, SymbolDispose, prototypeAsAny, Lifetime, StaticLifetime, WeakLifetime, Scope, AbstractDisposableResult, DisposableSuccess, DisposableFail, DisposableResult, QuickJSDeferredPromise, ModuleMemory, UnstableSymbol, DefaultIntrinsics, QuickJSIterator, ContextMemory, QuickJSContext, QuickJSRuntime, QuickJSEmscriptenModuleCallbacks, QuickJSModuleCallbacks, QuickJSWASMModule;
|
var __defProp2, __export2, QTS_DEBUG, errors_exports, QuickJSUnwrapError, QuickJSWrongOwner, QuickJSUseAfterFree, QuickJSNotImplemented, QuickJSAsyncifyError, QuickJSAsyncifySuspended, QuickJSMemoryLeakDetected, QuickJSEmscriptenModuleError, QuickJSUnknownIntrinsic, QuickJSPromisePending, QuickJSEmptyGetOwnPropertyNames, AwaitYield, UsingDisposable, SymbolDispose, prototypeAsAny, Lifetime, StaticLifetime, WeakLifetime, Scope, AbstractDisposableResult, DisposableSuccess, DisposableFail, DisposableResult, QuickJSDeferredPromise, ModuleMemory, DefaultIntrinsics, QuickJSIterator, ContextMemory, QuickJSContext, QuickJSRuntime, QuickJSEmscriptenModuleCallbacks, QuickJSModuleCallbacks, QuickJSWASMModule;
|
||||||
var init_chunk_JTKJZQYV = __esm({
|
var init_chunk_JTKJZQYV = __esm({
|
||||||
"registry/components/code/node_modules/quickjs-emscripten-core/dist/chunk-JTKJZQYV.mjs"() {
|
"registry/components/code/node_modules/quickjs-emscripten-core/dist/chunk-JTKJZQYV.mjs"() {
|
||||||
init_dist();
|
init_dist();
|
||||||
@@ -190,7 +195,7 @@ var init_chunk_JTKJZQYV = __esm({
|
|||||||
return this.dispose();
|
return this.dispose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
SymbolDispose = Symbol.dispose ?? Symbol.for("Symbol.dispose");
|
SymbolDispose = Symbol.dispose ?? /* @__PURE__ */ Symbol.for("Symbol.dispose");
|
||||||
prototypeAsAny = UsingDisposable.prototype;
|
prototypeAsAny = UsingDisposable.prototype;
|
||||||
prototypeAsAny[SymbolDispose] || (prototypeAsAny[SymbolDispose] = function() {
|
prototypeAsAny[SymbolDispose] || (prototypeAsAny[SymbolDispose] = function() {
|
||||||
return this.dispose();
|
return this.dispose();
|
||||||
@@ -409,7 +414,6 @@ Lifetime used`) : new QuickJSUseAfterFree("Lifetime not alive");
|
|||||||
return this.module._free(ptr), str;
|
return this.module._free(ptr), str;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
UnstableSymbol = Symbol("Unstable");
|
|
||||||
DefaultIntrinsics = Object.freeze({ BaseObjects: true, Date: true, Eval: true, StringNormalize: true, RegExp: true, JSON: true, Proxy: true, MapSet: true, TypedArrays: true, Promise: true });
|
DefaultIntrinsics = Object.freeze({ BaseObjects: true, Date: true, Eval: true, StringNormalize: true, RegExp: true, JSON: true, Proxy: true, MapSet: true, TypedArrays: true, Promise: true });
|
||||||
QuickJSIterator = class extends UsingDisposable {
|
QuickJSIterator = class extends UsingDisposable {
|
||||||
constructor(handle, context) {
|
constructor(handle, context) {
|
||||||
@@ -773,7 +777,7 @@ ${cause.stack}Host: ${hostStack}`), Object.assign(exception, rest), exception;
|
|||||||
}
|
}
|
||||||
return result.value;
|
return result.value;
|
||||||
}
|
}
|
||||||
[Symbol.for("nodejs.util.inspect.custom")]() {
|
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
||||||
return this.alive ? `${this.constructor.name} { ctx: ${this.ctx.value} rt: ${this.rt.value} }` : `${this.constructor.name} { disposed }`;
|
return this.alive ? `${this.constructor.name} { ctx: ${this.ctx.value} rt: ${this.rt.value} }` : `${this.constructor.name} { disposed }`;
|
||||||
}
|
}
|
||||||
getFunction(fn_id) {
|
getFunction(fn_id) {
|
||||||
@@ -909,7 +913,7 @@ ${cause.stack}Host: ${hostStack}`), Object.assign(exception, rest), exception;
|
|||||||
debugLog(...msg) {
|
debugLog(...msg) {
|
||||||
this._debugMode && console.log("quickjs-emscripten:", ...msg);
|
this._debugMode && console.log("quickjs-emscripten:", ...msg);
|
||||||
}
|
}
|
||||||
[Symbol.for("nodejs.util.inspect.custom")]() {
|
[/* @__PURE__ */ Symbol.for("nodejs.util.inspect.custom")]() {
|
||||||
return this.alive ? `${this.constructor.name} { rt: ${this.rt.value} }` : `${this.constructor.name} { disposed }`;
|
return this.alive ? `${this.constructor.name} { rt: ${this.rt.value} }` : `${this.constructor.name} { disposed }`;
|
||||||
}
|
}
|
||||||
getSystemContext() {
|
getSystemContext() {
|
||||||
@@ -1339,10 +1343,10 @@ async function QuickJSRaw(moduleArg = {}) {
|
|||||||
x ? (0 === h && (h = ra()), g[m] = x(e[m])) : g[m] = e[m];
|
x ? (0 === h && (h = ra()), g[m] = x(e[m])) : g[m] = e[m];
|
||||||
}
|
}
|
||||||
b = a(...g);
|
b = a(...g);
|
||||||
return b = function(k) {
|
return b = (function(k) {
|
||||||
0 !== h && sa(h);
|
0 !== h && sa(h);
|
||||||
return "string" === d ? R(k) : "boolean" === d ? !!k : k;
|
return "string" === d ? R(k) : "boolean" === d ? !!k : k;
|
||||||
}(b);
|
})(b);
|
||||||
};
|
};
|
||||||
c.wasmMemory ? r = c.wasmMemory : r = new WebAssembly.Memory({ initial: (c.INITIAL_MEMORY || 16777216) / 65536, maximum: 32768 });
|
c.wasmMemory ? r = c.wasmMemory : r = new WebAssembly.Memory({ initial: (c.INITIAL_MEMORY || 16777216) / 65536, maximum: 32768 });
|
||||||
K();
|
K();
|
||||||
@@ -1464,7 +1468,7 @@ async function QuickJSRaw(moduleArg = {}) {
|
|||||||
}, t: function(a, d) {
|
}, t: function(a, d) {
|
||||||
c.callbacks.freeHostRef(void 0, a, d);
|
c.callbacks.freeHostRef(void 0, a, d);
|
||||||
} }, Z;
|
} }, Z;
|
||||||
Z = await async function() {
|
Z = await (async function() {
|
||||||
function a(b) {
|
function a(b) {
|
||||||
b = Z = b.exports;
|
b = Z = b.exports;
|
||||||
c._malloc = b.v;
|
c._malloc = b.v;
|
||||||
@@ -1551,7 +1555,7 @@ async function QuickJSRaw(moduleArg = {}) {
|
|||||||
});
|
});
|
||||||
M ??= c.locateFile ? c.locateFile ? c.locateFile("emscripten-module.wasm", u) : u + "emscripten-module.wasm" : new URL("emscripten-module.wasm", import.meta.url).href;
|
M ??= c.locateFile ? c.locateFile ? c.locateFile("emscripten-module.wasm", u) : u + "emscripten-module.wasm" : new URL("emscripten-module.wasm", import.meta.url).href;
|
||||||
return a((await ea(d)).instance);
|
return a((await ea(d)).instance);
|
||||||
}();
|
})();
|
||||||
(function() {
|
(function() {
|
||||||
function a() {
|
function a() {
|
||||||
c.calledRun = true;
|
c.calledRun = true;
|
||||||
@@ -3034,7 +3038,7 @@ var Hono = class _Hono {
|
|||||||
var emptyParam = [];
|
var emptyParam = [];
|
||||||
function match(method, path) {
|
function match(method, path) {
|
||||||
const matchers = this.buildAllMatchers();
|
const matchers = this.buildAllMatchers();
|
||||||
const match2 = (method2, path2) => {
|
const match2 = ((method2, path2) => {
|
||||||
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
||||||
const staticMatch = matcher[2][path2];
|
const staticMatch = matcher[2][path2];
|
||||||
if (staticMatch) {
|
if (staticMatch) {
|
||||||
@@ -3046,7 +3050,7 @@ function match(method, path) {
|
|||||||
}
|
}
|
||||||
const index = match3.indexOf("", 1);
|
const index = match3.indexOf("", 1);
|
||||||
return [matcher[1][index], match3];
|
return [matcher[1][index], match3];
|
||||||
};
|
});
|
||||||
this.match = match2;
|
this.match = match2;
|
||||||
return match2(method, path);
|
return match2(method, path);
|
||||||
}
|
}
|
||||||
@@ -4008,7 +4012,7 @@ app.post("/", async (c) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var code_default = app;
|
var index_default = app;
|
||||||
export {
|
export {
|
||||||
code_default as default
|
index_default as default
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
var __defProp = Object.defineProperty;
|
var __defProp = Object.defineProperty;
|
||||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||||
var __esm = (fn, res) => function __init() {
|
var __esm = (fn, res, err) => function __init() {
|
||||||
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
if (err) throw err[0];
|
||||||
|
try {
|
||||||
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
||||||
|
} catch (e) {
|
||||||
|
throw err = [e], e;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
var __export = (target, all) => {
|
var __export = (target, all) => {
|
||||||
for (var name in all)
|
for (var name in all)
|
||||||
@@ -1501,7 +1506,7 @@ var init_hono_base = __esm({
|
|||||||
// cypher-executor/node_modules/.pnpm/hono@4.12.10/node_modules/hono/dist/router/reg-exp-router/matcher.js
|
// cypher-executor/node_modules/.pnpm/hono@4.12.10/node_modules/hono/dist/router/reg-exp-router/matcher.js
|
||||||
function match(method, path) {
|
function match(method, path) {
|
||||||
const matchers = this.buildAllMatchers();
|
const matchers = this.buildAllMatchers();
|
||||||
const match2 = (method2, path2) => {
|
const match2 = ((method2, path2) => {
|
||||||
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
||||||
const staticMatch = matcher[2][path2];
|
const staticMatch = matcher[2][path2];
|
||||||
if (staticMatch) {
|
if (staticMatch) {
|
||||||
@@ -1513,7 +1518,7 @@ function match(method, path) {
|
|||||||
}
|
}
|
||||||
const index = match3.indexOf("", 1);
|
const index = match3.indexOf("", 1);
|
||||||
return [matcher[1][index], match3];
|
return [matcher[1][index], match3];
|
||||||
};
|
});
|
||||||
this.match = match2;
|
this.match = match2;
|
||||||
return match2(method, path);
|
return match2(method, path);
|
||||||
}
|
}
|
||||||
@@ -7433,7 +7438,7 @@ var init_lib = __esm({
|
|||||||
...processCreateParams(params)
|
...processCreateParams(params)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
BRAND = Symbol("zod_brand");
|
BRAND = /* @__PURE__ */ Symbol("zod_brand");
|
||||||
ZodBranded = class extends ZodType {
|
ZodBranded = class extends ZodType {
|
||||||
_parse(input) {
|
_parse(input) {
|
||||||
const { ctx } = this._processInputParams(input);
|
const { ctx } = this._processInputParams(input);
|
||||||
@@ -7607,14 +7612,14 @@ var init_lib = __esm({
|
|||||||
onumber = () => numberType().optional();
|
onumber = () => numberType().optional();
|
||||||
oboolean = () => booleanType().optional();
|
oboolean = () => booleanType().optional();
|
||||||
coerce = {
|
coerce = {
|
||||||
string: (arg) => ZodString.create({ ...arg, coerce: true }),
|
string: ((arg) => ZodString.create({ ...arg, coerce: true })),
|
||||||
number: (arg) => ZodNumber.create({ ...arg, coerce: true }),
|
number: ((arg) => ZodNumber.create({ ...arg, coerce: true })),
|
||||||
boolean: (arg) => ZodBoolean.create({
|
boolean: ((arg) => ZodBoolean.create({
|
||||||
...arg,
|
...arg,
|
||||||
coerce: true
|
coerce: true
|
||||||
}),
|
})),
|
||||||
bigint: (arg) => ZodBigInt.create({ ...arg, coerce: true }),
|
bigint: ((arg) => ZodBigInt.create({ ...arg, coerce: true })),
|
||||||
date: (arg) => ZodDate.create({ ...arg, coerce: true })
|
date: ((arg) => ZodDate.create({ ...arg, coerce: true }))
|
||||||
};
|
};
|
||||||
NEVER = INVALID;
|
NEVER = INVALID;
|
||||||
z = /* @__PURE__ */ Object.freeze({
|
z = /* @__PURE__ */ Object.freeze({
|
||||||
@@ -7835,6 +7840,7 @@ var init_recipe_loader = __esm({
|
|||||||
super(message);
|
super(message);
|
||||||
this.recipe = recipe;
|
this.recipe = recipe;
|
||||||
}
|
}
|
||||||
|
recipe;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -8841,7 +8847,7 @@ function recordRecipeStats(env, recipeKeys, ok, at, ctx) {
|
|||||||
)
|
)
|
||||||
).then(() => void 0);
|
).then(() => void 0);
|
||||||
if (ctx?.waitUntil) ctx.waitUntil(promise);
|
if (ctx?.waitUntil) ctx.waitUntil(promise);
|
||||||
else ;
|
else void promise;
|
||||||
}
|
}
|
||||||
function generateToken() {
|
function generateToken() {
|
||||||
const tokenBytes = crypto.getRandomValues(new Uint8Array(16));
|
const tokenBytes = crypto.getRandomValues(new Uint8Array(16));
|
||||||
@@ -8883,7 +8889,7 @@ async function executeWebhookGraph(env, graph, triggerContext, token, apiKey, ct
|
|||||||
result.trace
|
result.trace
|
||||||
);
|
);
|
||||||
if (ctx?.waitUntil) ctx.waitUntil(statsPromise);
|
if (ctx?.waitUntil) ctx.waitUntil(statsPromise);
|
||||||
else ;
|
else void statsPromise;
|
||||||
}
|
}
|
||||||
return { success: true, data: result.data, duration_ms };
|
return { success: true, data: result.data, duration_ms };
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -8907,7 +8913,7 @@ async function executeWebhookGraph(env, graph, triggerContext, token, apiKey, ct
|
|||||||
err.trace
|
err.trace
|
||||||
);
|
);
|
||||||
if (ctx?.waitUntil) ctx.waitUntil(statsPromise);
|
if (ctx?.waitUntil) ctx.waitUntil(statsPromise);
|
||||||
else ;
|
else void statsPromise;
|
||||||
}
|
}
|
||||||
if (err instanceof ExecutionError) {
|
if (err instanceof ExecutionError) {
|
||||||
const traceFormatted = err.trace.map((s) => ({
|
const traceFormatted = err.trace.map((s) => ({
|
||||||
@@ -9374,6 +9380,7 @@ function extractTarget(input) {
|
|||||||
return typeof raw2 === "string" ? raw2 : JSON.stringify(raw2);
|
return typeof raw2 === "string" ? raw2 : JSON.stringify(raw2);
|
||||||
}
|
}
|
||||||
async function writeExecutionVerdict(env, workflowId, nodes, verdict, durationMs, message, input, apiKey) {
|
async function writeExecutionVerdict(env, workflowId, nodes, verdict, durationMs, message, input, apiKey) {
|
||||||
|
void nodes;
|
||||||
try {
|
try {
|
||||||
const { base, headers } = kbdbBase(env);
|
const { base, headers } = kbdbBase(env);
|
||||||
await fetch(`${base}/execution-log/record`, {
|
await fetch(`${base}/execution-log/record`, {
|
||||||
@@ -15654,10 +15661,10 @@ app.route("/", consoleAuthRouter);
|
|||||||
app.route("/", consoleDashboardRouter);
|
app.route("/", consoleDashboardRouter);
|
||||||
app.route("/", portalRouter);
|
app.route("/", portalRouter);
|
||||||
app.route("/", portalDataRouter);
|
app.route("/", portalDataRouter);
|
||||||
var src_default = {
|
var index_default = {
|
||||||
fetch: app.fetch,
|
fetch: app.fetch,
|
||||||
scheduled: handleScheduled
|
scheduled: handleScheduled
|
||||||
};
|
};
|
||||||
export {
|
export {
|
||||||
src_default as default
|
index_default as default
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1427,7 +1427,7 @@ var Hono = class _Hono {
|
|||||||
var emptyParam = [];
|
var emptyParam = [];
|
||||||
function match(method, path) {
|
function match(method, path) {
|
||||||
const matchers = this.buildAllMatchers();
|
const matchers = this.buildAllMatchers();
|
||||||
const match2 = (method2, path2) => {
|
const match2 = ((method2, path2) => {
|
||||||
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
||||||
const staticMatch = matcher[2][path2];
|
const staticMatch = matcher[2][path2];
|
||||||
if (staticMatch) {
|
if (staticMatch) {
|
||||||
@@ -1439,7 +1439,7 @@ function match(method, path) {
|
|||||||
}
|
}
|
||||||
const index = match3.indexOf("", 1);
|
const index = match3.indexOf("", 1);
|
||||||
return [matcher[1][index], match3];
|
return [matcher[1][index], match3];
|
||||||
};
|
});
|
||||||
this.match = match2;
|
this.match = match2;
|
||||||
return match2(method, path);
|
return match2(method, path);
|
||||||
}
|
}
|
||||||
@@ -2522,7 +2522,7 @@ app.post("/", async (c) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
var src_default = app;
|
var index_default = app;
|
||||||
async function runWasm(input) {
|
async function runWasm(input) {
|
||||||
const hostFunctions = {
|
const hostFunctions = {
|
||||||
http_request: async (url, method, headersJson, body) => {
|
http_request: async (url, method, headersJson, body) => {
|
||||||
@@ -2568,5 +2568,5 @@ async function runWasm(input) {
|
|||||||
return JSON.parse(stdout);
|
return JSON.parse(stdout);
|
||||||
}
|
}
|
||||||
export {
|
export {
|
||||||
src_default as default
|
index_default as default
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1444,7 +1444,7 @@ var Hono = class _Hono {
|
|||||||
var emptyParam = [];
|
var emptyParam = [];
|
||||||
function match(method, path) {
|
function match(method, path) {
|
||||||
const matchers = this.buildAllMatchers();
|
const matchers = this.buildAllMatchers();
|
||||||
const match2 = (method2, path2) => {
|
const match2 = ((method2, path2) => {
|
||||||
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
||||||
const staticMatch = matcher[2][path2];
|
const staticMatch = matcher[2][path2];
|
||||||
if (staticMatch) {
|
if (staticMatch) {
|
||||||
@@ -1456,7 +1456,7 @@ function match(method, path) {
|
|||||||
}
|
}
|
||||||
const index = match3.indexOf("", 1);
|
const index = match3.indexOf("", 1);
|
||||||
return [matcher[1][index], match3];
|
return [matcher[1][index], match3];
|
||||||
};
|
});
|
||||||
this.match = match2;
|
this.match = match2;
|
||||||
return match2(method, path);
|
return match2(method, path);
|
||||||
}
|
}
|
||||||
@@ -4157,7 +4157,7 @@ app.route("/recipe-stats", recipeStatRoutes);
|
|||||||
app.route("/execution-log", executionLogRoutes);
|
app.route("/execution-log", executionLogRoutes);
|
||||||
app.route("/embed", embedRoutes);
|
app.route("/embed", embedRoutes);
|
||||||
app.route("/map", mapRoutes);
|
app.route("/map", mapRoutes);
|
||||||
var src_default = app;
|
var index_default = app;
|
||||||
export {
|
export {
|
||||||
src_default as default
|
index_default as default
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -5,7 +5,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
|
|||||||
var __getProtoOf = Object.getPrototypeOf;
|
var __getProtoOf = Object.getPrototypeOf;
|
||||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||||
var __commonJS = (cb, mod) => function __require() {
|
var __commonJS = (cb, mod) => function __require() {
|
||||||
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
try {
|
||||||
|
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
||||||
|
} catch (e) {
|
||||||
|
throw mod = 0, e;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
var __export = (target, all) => {
|
var __export = (target, all) => {
|
||||||
for (var name in all)
|
for (var name in all)
|
||||||
@@ -8223,7 +8227,7 @@ var Hono = class _Hono {
|
|||||||
var emptyParam = [];
|
var emptyParam = [];
|
||||||
function match(method, path) {
|
function match(method, path) {
|
||||||
const matchers = this.buildAllMatchers();
|
const matchers = this.buildAllMatchers();
|
||||||
const match2 = (method2, path2) => {
|
const match2 = ((method2, path2) => {
|
||||||
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
|
||||||
const staticMatch = matcher[2][path2];
|
const staticMatch = matcher[2][path2];
|
||||||
if (staticMatch) {
|
if (staticMatch) {
|
||||||
@@ -8235,7 +8239,7 @@ function match(method, path) {
|
|||||||
}
|
}
|
||||||
const index = match3.indexOf("", 1);
|
const index = match3.indexOf("", 1);
|
||||||
return [matcher[1][index], match3];
|
return [matcher[1][index], match3];
|
||||||
};
|
});
|
||||||
this.match = match2;
|
this.match = match2;
|
||||||
return match2(method, path);
|
return match2(method, path);
|
||||||
}
|
}
|
||||||
@@ -12959,7 +12963,7 @@ ZodNaN.create = (params) => {
|
|||||||
...processCreateParams(params)
|
...processCreateParams(params)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
var BRAND = Symbol("zod_brand");
|
var BRAND = /* @__PURE__ */ Symbol("zod_brand");
|
||||||
var ZodBranded = class extends ZodType {
|
var ZodBranded = class extends ZodType {
|
||||||
_parse(input) {
|
_parse(input) {
|
||||||
const { ctx } = this._processInputParams(input);
|
const { ctx } = this._processInputParams(input);
|
||||||
@@ -13161,14 +13165,14 @@ var ostring = () => stringType().optional();
|
|||||||
var onumber = () => numberType().optional();
|
var onumber = () => numberType().optional();
|
||||||
var oboolean = () => booleanType().optional();
|
var oboolean = () => booleanType().optional();
|
||||||
var coerce = {
|
var coerce = {
|
||||||
string: (arg) => ZodString.create({ ...arg, coerce: true }),
|
string: ((arg) => ZodString.create({ ...arg, coerce: true })),
|
||||||
number: (arg) => ZodNumber.create({ ...arg, coerce: true }),
|
number: ((arg) => ZodNumber.create({ ...arg, coerce: true })),
|
||||||
boolean: (arg) => ZodBoolean.create({
|
boolean: ((arg) => ZodBoolean.create({
|
||||||
...arg,
|
...arg,
|
||||||
coerce: true
|
coerce: true
|
||||||
}),
|
})),
|
||||||
bigint: (arg) => ZodBigInt.create({ ...arg, coerce: true }),
|
bigint: ((arg) => ZodBigInt.create({ ...arg, coerce: true })),
|
||||||
date: (arg) => ZodDate.create({ ...arg, coerce: true })
|
date: ((arg) => ZodDate.create({ ...arg, coerce: true }))
|
||||||
};
|
};
|
||||||
var NEVER = INVALID;
|
var NEVER = INVALID;
|
||||||
|
|
||||||
@@ -13219,7 +13223,6 @@ function $constructor(name, initializer3, params) {
|
|||||||
Object.defineProperty(_, "name", { value: name });
|
Object.defineProperty(_, "name", { value: name });
|
||||||
return _;
|
return _;
|
||||||
}
|
}
|
||||||
var $brand = Symbol("zod_brand");
|
|
||||||
var $ZodAsyncError = class extends Error {
|
var $ZodAsyncError = class extends Error {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);
|
super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);
|
||||||
@@ -15723,8 +15726,6 @@ function en_default2() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mcp/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/registries.js
|
// mcp/node_modules/.pnpm/zod@3.25.76/node_modules/zod/v4/core/registries.js
|
||||||
var $output = Symbol("ZodOutput");
|
|
||||||
var $input = Symbol("ZodInput");
|
|
||||||
var $ZodRegistry = class {
|
var $ZodRegistry = class {
|
||||||
constructor() {
|
constructor() {
|
||||||
this._map = /* @__PURE__ */ new Map();
|
this._map = /* @__PURE__ */ new Map();
|
||||||
@@ -17002,10 +17003,10 @@ var ZodMiniType = /* @__PURE__ */ $constructor("ZodMiniType", (inst, def) => {
|
|||||||
};
|
};
|
||||||
inst.clone = (_def, params) => clone(inst, _def, params);
|
inst.clone = (_def, params) => clone(inst, _def, params);
|
||||||
inst.brand = () => inst;
|
inst.brand = () => inst;
|
||||||
inst.register = (reg, meta) => {
|
inst.register = ((reg, meta) => {
|
||||||
reg.add(inst, meta);
|
reg.add(inst, meta);
|
||||||
return inst;
|
return inst;
|
||||||
};
|
});
|
||||||
});
|
});
|
||||||
var ZodMiniObject = /* @__PURE__ */ $constructor("ZodMiniObject", (inst, def) => {
|
var ZodMiniObject = /* @__PURE__ */ $constructor("ZodMiniObject", (inst, def) => {
|
||||||
$ZodObject.init(inst, def);
|
$ZodObject.init(inst, def);
|
||||||
@@ -17268,10 +17269,10 @@ var ZodType2 = /* @__PURE__ */ $constructor("ZodType", (inst, def) => {
|
|||||||
};
|
};
|
||||||
inst.clone = (def2, params) => clone(inst, def2, params);
|
inst.clone = (def2, params) => clone(inst, def2, params);
|
||||||
inst.brand = () => inst;
|
inst.brand = () => inst;
|
||||||
inst.register = (reg, meta) => {
|
inst.register = ((reg, meta) => {
|
||||||
reg.add(inst, meta);
|
reg.add(inst, meta);
|
||||||
return inst;
|
return inst;
|
||||||
};
|
});
|
||||||
inst.parse = (data, params) => parse2(inst, data, params, { callee: inst.parse });
|
inst.parse = (data, params) => parse2(inst, data, params, { callee: inst.parse });
|
||||||
inst.safeParse = (data, params) => safeParse3(inst, data, params);
|
inst.safeParse = (data, params) => safeParse3(inst, data, params);
|
||||||
inst.parseAsync = async (data, params) => parseAsync2(inst, data, params, { callee: inst.parseAsync });
|
inst.parseAsync = async (data, params) => parseAsync2(inst, data, params, { callee: inst.parseAsync });
|
||||||
@@ -19243,11 +19244,13 @@ function assertCompleteRequestPrompt(request) {
|
|||||||
if (request.params.ref.type !== "ref/prompt") {
|
if (request.params.ref.type !== "ref/prompt") {
|
||||||
throw new TypeError(`Expected CompleteRequestPrompt, but got ${request.params.ref.type}`);
|
throw new TypeError(`Expected CompleteRequestPrompt, but got ${request.params.ref.type}`);
|
||||||
}
|
}
|
||||||
|
void request;
|
||||||
}
|
}
|
||||||
function assertCompleteRequestResourceTemplate(request) {
|
function assertCompleteRequestResourceTemplate(request) {
|
||||||
if (request.params.ref.type !== "ref/resource") {
|
if (request.params.ref.type !== "ref/resource") {
|
||||||
throw new TypeError(`Expected CompleteRequestResourceTemplate, but got ${request.params.ref.type}`);
|
throw new TypeError(`Expected CompleteRequestResourceTemplate, but got ${request.params.ref.type}`);
|
||||||
}
|
}
|
||||||
|
void request;
|
||||||
}
|
}
|
||||||
var CompleteResultSchema = ResultSchema.extend({
|
var CompleteResultSchema = ResultSchema.extend({
|
||||||
completion: looseObject({
|
completion: looseObject({
|
||||||
@@ -19400,7 +19403,7 @@ function isTerminal(status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mcp/node_modules/.pnpm/zod-to-json-schema@3.25.2_zod@3.25.76/node_modules/zod-to-json-schema/dist/esm/Options.js
|
// mcp/node_modules/.pnpm/zod-to-json-schema@3.25.2_zod@3.25.76/node_modules/zod-to-json-schema/dist/esm/Options.js
|
||||||
var ignoreOverride = Symbol("Let zodToJsonSchema decide on which parser to use");
|
var ignoreOverride = /* @__PURE__ */ Symbol("Let zodToJsonSchema decide on which parser to use");
|
||||||
var defaultOptions = {
|
var defaultOptions = {
|
||||||
name: void 0,
|
name: void 0,
|
||||||
$refStrategy: "root",
|
$refStrategy: "root",
|
||||||
@@ -22376,7 +22379,7 @@ var Server = class extends Protocol {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// mcp/node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
|
// mcp/node_modules/.pnpm/@modelcontextprotocol+sdk@1.29.0_zod@3.25.76/node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
|
||||||
var COMPLETABLE_SYMBOL = Symbol.for("mcp.completable");
|
var COMPLETABLE_SYMBOL = /* @__PURE__ */ Symbol.for("mcp.completable");
|
||||||
function isCompletable(schema4) {
|
function isCompletable(schema4) {
|
||||||
return !!schema4 && typeof schema4 === "object" && COMPLETABLE_SYMBOL in schema4;
|
return !!schema4 && typeof schema4 === "object" && COMPLETABLE_SYMBOL in schema4;
|
||||||
}
|
}
|
||||||
@@ -24691,13 +24694,13 @@ function registerAllIntrospectionTools(server, env) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// mcp/node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/nodes/identity.js
|
// mcp/node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/nodes/identity.js
|
||||||
var ALIAS = Symbol.for("yaml.alias");
|
var ALIAS = /* @__PURE__ */ Symbol.for("yaml.alias");
|
||||||
var DOC = Symbol.for("yaml.document");
|
var DOC = /* @__PURE__ */ Symbol.for("yaml.document");
|
||||||
var MAP = Symbol.for("yaml.map");
|
var MAP = /* @__PURE__ */ Symbol.for("yaml.map");
|
||||||
var PAIR = Symbol.for("yaml.pair");
|
var PAIR = /* @__PURE__ */ Symbol.for("yaml.pair");
|
||||||
var SCALAR = Symbol.for("yaml.scalar");
|
var SCALAR = /* @__PURE__ */ Symbol.for("yaml.scalar");
|
||||||
var SEQ = Symbol.for("yaml.seq");
|
var SEQ = /* @__PURE__ */ Symbol.for("yaml.seq");
|
||||||
var NODE_TYPE = Symbol.for("yaml.node.type");
|
var NODE_TYPE = /* @__PURE__ */ Symbol.for("yaml.node.type");
|
||||||
var isAlias = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === ALIAS;
|
var isAlias = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === ALIAS;
|
||||||
var isDocument = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === DOC;
|
var isDocument = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === DOC;
|
||||||
var isMap = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === MAP;
|
var isMap = (node) => !!node && typeof node === "object" && node[NODE_TYPE] === MAP;
|
||||||
@@ -24727,9 +24730,9 @@ function isNode(node) {
|
|||||||
var hasAnchor = (node) => (isScalar(node) || isCollection(node)) && !!node.anchor;
|
var hasAnchor = (node) => (isScalar(node) || isCollection(node)) && !!node.anchor;
|
||||||
|
|
||||||
// mcp/node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/visit.js
|
// mcp/node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/visit.js
|
||||||
var BREAK = Symbol("break visit");
|
var BREAK = /* @__PURE__ */ Symbol("break visit");
|
||||||
var SKIP = Symbol("skip children");
|
var SKIP = /* @__PURE__ */ Symbol("skip children");
|
||||||
var REMOVE = Symbol("remove node");
|
var REMOVE = /* @__PURE__ */ Symbol("remove node");
|
||||||
function visit(node, visitor) {
|
function visit(node, visitor) {
|
||||||
const visitor_ = initVisitor(visitor);
|
const visitor_ = initVisitor(visitor);
|
||||||
if (isDocument(node)) {
|
if (isDocument(node)) {
|
||||||
@@ -29322,9 +29325,9 @@ ${end.comment}` : end.comment;
|
|||||||
};
|
};
|
||||||
|
|
||||||
// mcp/node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/parse/cst-visit.js
|
// mcp/node_modules/.pnpm/yaml@2.9.0/node_modules/yaml/browser/dist/parse/cst-visit.js
|
||||||
var BREAK2 = Symbol("break visit");
|
var BREAK2 = /* @__PURE__ */ Symbol("break visit");
|
||||||
var SKIP2 = Symbol("skip children");
|
var SKIP2 = /* @__PURE__ */ Symbol("skip children");
|
||||||
var REMOVE2 = Symbol("remove item");
|
var REMOVE2 = /* @__PURE__ */ Symbol("remove item");
|
||||||
function visit2(cst, visitor) {
|
function visit2(cst, visitor) {
|
||||||
if ("type" in cst && cst.type === "document")
|
if ("type" in cst && cst.type === "document")
|
||||||
cst = { start: cst.start, value: cst.value };
|
cst = { start: cst.start, value: cst.value };
|
||||||
@@ -33596,7 +33599,7 @@ app.post("/", partnerAuthMiddleware, async (c) => {
|
|||||||
const identity = resolveKnowledgeIdentity(c.get("auth_path"), c.get("portal"));
|
const identity = resolveKnowledgeIdentity(c.get("auth_path"), c.get("portal"));
|
||||||
return handleMcpRequest(c.req.raw, c.env, orgNamespace, partnerToken, identity);
|
return handleMcpRequest(c.req.raw, c.env, orgNamespace, partnerToken, identity);
|
||||||
});
|
});
|
||||||
var src_default = _app;
|
var index_default = _app;
|
||||||
export {
|
export {
|
||||||
src_default as default
|
index_default as default
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"schema": 1,
|
"schema": 1,
|
||||||
"built_for": "arcrun-tier2-worker-artifacts",
|
"built_for": "arcrun-tier2-worker-artifacts",
|
||||||
"generated_at": "2026-08-12T16:16:33.433Z",
|
"generated_at": "2026-08-12T16:25:19.210Z",
|
||||||
"repo_head": "b223a698844be289c1b01f99eb34a8e2ac85bb74",
|
"repo_head": "2fcae722e7d7eab3c8b39744b87ae1344d23ba2a",
|
||||||
"repo_dirty": false,
|
"repo_dirty": false,
|
||||||
"workers": [
|
"workers": [
|
||||||
{
|
{
|
||||||
@@ -11,8 +11,8 @@
|
|||||||
"source_commit": "b223a698844be289c1b01f99eb34a8e2ac85bb74",
|
"source_commit": "b223a698844be289c1b01f99eb34a8e2ac85bb74",
|
||||||
"main_module": "worker.mjs",
|
"main_module": "worker.mjs",
|
||||||
"main_file": "arcrun-cypher-executor/worker.mjs",
|
"main_file": "arcrun-cypher-executor/worker.mjs",
|
||||||
"js_bytes": 588397,
|
"js_bytes": 588587,
|
||||||
"content_sha256": "e0026a23792f8b6b02e35c91081604b9a761c608a2cae0e6ee8ab0f34a484501",
|
"content_sha256": "c43728d21251f7835497d7dc40a3e702526a70b33406b617ed64126ffb16e1e0",
|
||||||
"modules": [],
|
"modules": [],
|
||||||
"compat_date": "2025-02-19",
|
"compat_date": "2025-02-19",
|
||||||
"compat_flags": [
|
"compat_flags": [
|
||||||
@@ -61,8 +61,8 @@
|
|||||||
"source_commit": "f87d0e92f49690253e7c89c5badc82a08eb5d21b",
|
"source_commit": "f87d0e92f49690253e7c89c5badc82a08eb5d21b",
|
||||||
"main_module": "worker.mjs",
|
"main_module": "worker.mjs",
|
||||||
"main_file": "arcrun-kbdb/worker.mjs",
|
"main_file": "arcrun-kbdb/worker.mjs",
|
||||||
"js_bytes": 149791,
|
"js_bytes": 149797,
|
||||||
"content_sha256": "9c6d41895d78cbf3048fb539690591c86071b7d6ebf7fb0b8b51607c6b1f8ee9",
|
"content_sha256": "8b23853cbc88aee0ca15ef20ca46e92bd8e75064cd311af2847f4d51811960b1",
|
||||||
"modules": [],
|
"modules": [],
|
||||||
"compat_date": "2025-02-19",
|
"compat_date": "2025-02-19",
|
||||||
"compat_flags": [
|
"compat_flags": [
|
||||||
@@ -90,8 +90,8 @@
|
|||||||
"source_commit": "1e85dfb49b0e8d81c0854781d93ee4e6a300c7b3",
|
"source_commit": "1e85dfb49b0e8d81c0854781d93ee4e6a300c7b3",
|
||||||
"main_module": "worker.mjs",
|
"main_module": "worker.mjs",
|
||||||
"main_file": "arcrun-http-request/worker.mjs",
|
"main_file": "arcrun-http-request/worker.mjs",
|
||||||
"js_bytes": 80073,
|
"js_bytes": 80079,
|
||||||
"content_sha256": "9a9dcb71879a7bdfd9fec1bd94eb9742e12cb63733d822ce63eeb1be30008d15",
|
"content_sha256": "cdd97364f277587cbade69e09bb40812c68f26a1e8bc9aa632c65b1b962b0b85",
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
"name": "component.wasm",
|
"name": "component.wasm",
|
||||||
@@ -122,8 +122,8 @@
|
|||||||
"source_commit": "621cb8d948d61be6202063fd02effb3f538437fe",
|
"source_commit": "621cb8d948d61be6202063fd02effb3f538437fe",
|
||||||
"main_module": "worker.mjs",
|
"main_module": "worker.mjs",
|
||||||
"main_file": "arcrun-code/worker.mjs",
|
"main_file": "arcrun-code/worker.mjs",
|
||||||
"js_bytes": 153671,
|
"js_bytes": 153758,
|
||||||
"content_sha256": "285a7406ec694ae47dccfaf48517f712c74d207a1689dffa15c39f1555b45be5",
|
"content_sha256": "751634a3fc9a99cc2da662026818d754c48f031d10bff3b3be2d3a8ee2311bd6",
|
||||||
"modules": [
|
"modules": [
|
||||||
{
|
{
|
||||||
"name": "quickjs.wasm",
|
"name": "quickjs.wasm",
|
||||||
@@ -151,8 +151,8 @@
|
|||||||
"source_commit": "10d150ac2b4385af95a457f3c411430c4a146cf9",
|
"source_commit": "10d150ac2b4385af95a457f3c411430c4a146cf9",
|
||||||
"main_module": "worker.mjs",
|
"main_module": "worker.mjs",
|
||||||
"main_file": "arcrun-mcp/worker.mjs",
|
"main_file": "arcrun-mcp/worker.mjs",
|
||||||
"js_bytes": 1179229,
|
"js_bytes": 1179487,
|
||||||
"content_sha256": "3ebc0d441bc04ae56a1205507da701f93bed9efa9b1e53c1777c04cbef5bdb67",
|
"content_sha256": "1cd4c4d079d72bf7cba7c490ba6a88476f70b3ea51af7e5c93f9a184ae3c0ce6",
|
||||||
"modules": [],
|
"modules": [],
|
||||||
"compat_date": "2024-11-27",
|
"compat_date": "2024-11-27",
|
||||||
"compat_flags": [
|
"compat_flags": [
|
||||||
|
|||||||
+94
-8
@@ -5,20 +5,86 @@ import { buildLibraryMapInstructions } from "./lib/library-map.js";
|
|||||||
import type { KnowledgeIdentity } from "./lib/portal-client.js";
|
import type { KnowledgeIdentity } from "./lib/portal-client.js";
|
||||||
import { Env } from "./types.js";
|
import { Env } from "./types.js";
|
||||||
|
|
||||||
export async function handleMcpRequest(
|
/**
|
||||||
request: Request,
|
* 【這條連線上有主人的知識庫】——**靜態、必定出現**的指路段(2026-08-13,leo)。
|
||||||
|
*
|
||||||
|
* leo 原話:「它應該是**你的知識來源**⋯⋯**沒有它你是瞎的**,從你對 Arcrun 的認知就知道
|
||||||
|
* 你的內建 Memory 是沒用的。」
|
||||||
|
*
|
||||||
|
* 為什麼要獨立成一段、而且**不准依賴任何查詢**:
|
||||||
|
* 這條連線本來就查得到答案——`kbdb_search(q="Arcrun 是什麼")` 當天實測回 33 筆真答案。
|
||||||
|
* 但同一天,一個連著這條 MCP 的 session 為了回答同一題,去讀了 682 行原始碼——
|
||||||
|
* **因為它不知道這裡有知識庫可查**。instructions 裡唯一提到知識的,是下面那段【藏書地圖】,
|
||||||
|
* 而那段是**選配的**(1500ms 逾時、失敗回 null);那條 portal 連線就沒收到它。
|
||||||
|
* ⇒ **「這裡有知識庫、怎麼查」這句話本身被綁在一個會無聲消失的段落上,才是 AI 開場全瞎的成因。**
|
||||||
|
* 所以它跟下面的 Arcrun 指路段同級:靜態常數,KBDB 掛了、地圖抓不到,它照樣出現。
|
||||||
|
*/
|
||||||
|
const KNOWLEDGE_FIRST = [
|
||||||
|
"【這條連線上有主人的知識庫——先查它,再查別的】",
|
||||||
|
"",
|
||||||
|
"這條 MCP 連線後面接著一個 **KBDB 知識庫**:這台實例的主人長期累積的筆記、決策、",
|
||||||
|
"踩過的坑、專案現況、skill 與工作流紀錄,都在裡面。**你不是從零開始的**——",
|
||||||
|
"你對這些專案的內建印象多半是錯的或過時的,庫裡那份才是主人認的版本。",
|
||||||
|
"",
|
||||||
|
"🔴 **有人問你「X 是什麼/為什麼這樣做/之前怎麼決定的/現在做到哪」——",
|
||||||
|
"你的第一個動作是 `kbdb_search`,不是 grep 原始碼、不是上網搜、不是回答「我不知道」。**",
|
||||||
|
"",
|
||||||
|
'- `kbdb_search({ q: "Arcrun 是什麼" })` — 關鍵字查(預設 `mode:\'keyword\'`,基本盤永遠可用)。',
|
||||||
|
" 換幾組講法再放棄;想要語義相似度用 `mode:'semantic'`。**這一支是你的第一站。**",
|
||||||
|
"- `kbdb_get_map()` — 不知道該進哪個庫時先看藏書地圖(下面若有【藏書地圖】就是它的快照)。",
|
||||||
|
'- `kbdb_graph_neighbors({ subject: "Arcrun" })` — 查某個東西跟誰有關係(三元組遍歷)。',
|
||||||
|
"- `kbdb_list_templates` / `kbdb_query` — 按 template 取整批結構化資料。",
|
||||||
|
"",
|
||||||
|
"🔴 **這三件事不可以講成同一句**(講成同一句就是在騙人):",
|
||||||
|
"① 「知識庫裡沒有」 ② 「我沒查」 ③ 「地圖沒取到/某庫顯示 0」。",
|
||||||
|
"查過真的沒有 → 明說「知識庫裡查不到,以下是我從原始碼/網路推的」,再去讀 code 或上網。",
|
||||||
|
"**沒查就回答=拿你的猜測冒充主人的知識,那是這條連線上最嚴重的錯。**",
|
||||||
|
"",
|
||||||
|
"🔴 **地圖是索引,不是庫存清單**:某庫顯示 `0 triplets`、或下面整段【藏書地圖】沒出現,",
|
||||||
|
"都**不代表**沒有知識(可能只是還沒重算、或這次沒抓到)。要知道有沒有,只有一個方法:`kbdb_search` 查過。",
|
||||||
|
"同理,任何工具回 401/連不上/沒權限,那是**讀不到**,不是**不存在**——照它給的 next_actions 修,",
|
||||||
|
"別把它改口講成「這裡沒有」(`arcrun_get_skill` 曾把 KBDB 的 401 講成「skill 不存在」,就是這個病)。",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
/** 地圖沒拿到時的**明講**(不可靜默):「沒取到」和「這裡沒有知識」不可以長得一樣。 */
|
||||||
|
const MAP_UNAVAILABLE_NOTE = [
|
||||||
|
"【藏書地圖:這次沒取到】",
|
||||||
|
"地圖抓取逾時/回錯/或它回報的清單是空的(也可能只是還沒重算過)。",
|
||||||
|
"🔴 **這是「地圖沒拿到」,不是「這裡沒有知識」。** 上面那條規則照舊:",
|
||||||
|
"要知道庫裡有什麼,直接 `kbdb_search`;想再抓一次地圖呼叫 `kbdb_get_map()`(它會回報真正的原因)。",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
/** 舊 token(stale):拿不到地圖是**身分問題**,同樣要明講,並給可執行的修法。 */
|
||||||
|
const MAP_STALE_NOTE = [
|
||||||
|
"【藏書地圖:拿不到,因為這條連線是舊版簽發的 token】",
|
||||||
|
"這條連線的 token 沒帶登入者身分,`kbdb_*` 會回 `identity_missing`。",
|
||||||
|
"🔴 **這不代表知識庫是空的**——是這條連線還沒認得你。",
|
||||||
|
"仍然先呼叫一次 `kbdb_search` 確認錯誤碼;若真的是 `identity_missing`,",
|
||||||
|
"請使用者到 claude.ai → Settings → Connectors 把這個 connector 重新連線一次(重新輸入 Portal 帳密),",
|
||||||
|
"**不要改口說「查不到資料」或自己去猜答案。**",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 組這條連線的 server instructions(`initialize` 時送出,**AI 沒辦法自己再要一次** ⇒ 必須一次到位)。
|
||||||
|
*
|
||||||
|
* 匯出給測試:三種情境(地圖抓得到/抓不到/舊 token)各印一份完整字串,逐份確認
|
||||||
|
* 「一個什麼都不知道的 AI 讀完,下一個動作會不會是去查知識庫」。
|
||||||
|
*/
|
||||||
|
export async function buildServerInstructions(
|
||||||
env: Env,
|
env: Env,
|
||||||
orgNamespace: string,
|
|
||||||
partnerToken: string,
|
|
||||||
identity: KnowledgeIdentity,
|
identity: KnowledgeIdentity,
|
||||||
): Promise<Response> {
|
): Promise<string> {
|
||||||
// library-map SDD M4(design §4/§6):連線時把全館藏書地圖嵌進 server instructions,
|
// library-map SDD M4(design §4/§6):連線時把全館藏書地圖嵌進 server instructions,
|
||||||
// session 一開就知道館裡有哪些庫(push 零查詢)。builder 內建 timeout+isolate TTL 快取
|
// session 一開就知道館裡有哪些庫(push 零查詢)。builder 內建 timeout+isolate TTL 快取
|
||||||
//(選型理由見 lib/library-map.ts 檔頭);任何失敗回 null → 靜默略過,絕不擋 MCP 連線(鐵律)。
|
//(選型理由見 lib/library-map.ts 檔頭);任何失敗回 null → 絕不擋 MCP 連線(鐵律)。
|
||||||
//
|
//
|
||||||
// 🔴 2026-08-12:以帳密連線時**改用登入者的身分**組地圖——否則 instructions 會把
|
// 🔴 2026-08-12:以帳密連線時**改用登入者的身分**組地圖——否則 instructions 會把
|
||||||
// 整個知識庫的庫名一次推給一個可能只有部分權限的帳號(地圖本身就是情報)。
|
// 整個知識庫的庫名一次推給一個可能只有部分權限的帳號(地圖本身就是情報)。
|
||||||
// 快取也因此改成 per-session key(見 lib/library-map.ts)。
|
// 快取也因此改成 per-session key(見 lib/library-map.ts)。
|
||||||
|
//
|
||||||
|
// 🔴 2026-08-13:失敗**不再靜默略過**。原本 null → 整段消失,於是「地圖沒取到」與
|
||||||
|
// 「這裡沒有知識」在 AI 眼裡長得一模一樣(Arcrun#109 同一族:保險拒絕了 vs 程式碼不存在,
|
||||||
|
// 畫面上都是沉默)。現在改成印一句明話。鐵律沒變——**失敗仍然不擋連線**,只是不再無聲。
|
||||||
const mapInstructions = await buildLibraryMapInstructions(env, identity);
|
const mapInstructions = await buildLibraryMapInstructions(env, identity);
|
||||||
|
|
||||||
// 2026-07-30(leo 問「人類說『幫我用 arcrun 寫 xxx』,Haiku 會知道要用這些資源嗎?
|
// 2026-07-30(leo 問「人類說『幫我用 arcrun 寫 xxx』,Haiku 會知道要用這些資源嗎?
|
||||||
@@ -39,9 +105,13 @@ export async function handleMcpRequest(
|
|||||||
"",
|
"",
|
||||||
"1. `arcrun_get_skill('write_intent_workflow')` — **必讀第一支**。",
|
"1. `arcrun_get_skill('write_intent_workflow')` — **必讀第一支**。",
|
||||||
" 教你用 `>>` 寫「意圖工作流」。你**不需要先知道有哪些零件**,先寫意圖。",
|
" 教你用 `>>` 寫「意圖工作流」。你**不需要先知道有哪些零件**,先寫意圖。",
|
||||||
|
" ⚠️ 這支若回錯(401/連不上/`kbdb_unreachable`),那是**這條連線讀不到 KBDB**,",
|
||||||
|
" **不是 skill 不存在**——同一份內容用 `kbdb_search({ q: 'skill-write_intent_workflow' })` 撈得到。",
|
||||||
"2. `arcrun_whoami()` — 確認連到哪個帳號(勿自行 curl 猜帳號 URL)。",
|
"2. `arcrun_whoami()` — 確認連到哪個帳號(勿自行 curl 猜帳號 URL)。",
|
||||||
"3. 把意圖丟 `POST /cypher/search` 或 `arcrun_validate_yaml` — 系統告訴你哪些零件存在。",
|
"3. 把意圖丟 `POST /cypher/search` 或 `arcrun_validate_yaml` — 系統告訴你哪些零件存在。",
|
||||||
"4. 卡住/不知道該查什麼 → `arcrun_get_skill('INDEX')`(全館導航:什麼問題查哪裡+已知的坑)。",
|
"4. 卡住/不知道該查什麼 → 先 `arcrun_list_skills()` **看這台實例真的有哪幾支**,再挑一支讀。",
|
||||||
|
" (2026-08-13 實測:不同實例 seed 的 skill 不一樣,有的實例只有兩支、連 `INDEX` 都沒有。",
|
||||||
|
" **不要照教材直接指名一個 slug** ——先列清單,或 `kbdb_search({ q: 'skill' })` 直接在庫裡找。)",
|
||||||
"5. 缺零件時:缺 API → 寫 recipe(`arcrun_recipe_push`);缺能力 → 投稿零件 PR。",
|
"5. 缺零件時:缺 API → 寫 recipe(`arcrun_recipe_push`);缺能力 → 投稿零件 PR。",
|
||||||
" 🔴 **不要因為查不到零件就改寫成 `code` 節點**——那叫「腹語術」(表面用 Arcrun、",
|
" 🔴 **不要因為查不到零件就改寫成 `code` 節點**——那叫「腹語術」(表面用 Arcrun、",
|
||||||
" 實際全寫 JS)。`code` 只用於局部整形(例:剝掉 LLM 回應的雜訊)。",
|
" 實際全寫 JS)。`code` 只用於局部整形(例:剝掉 LLM 回應的雜訊)。",
|
||||||
@@ -58,7 +128,23 @@ export async function handleMcpRequest(
|
|||||||
"第一個節點固定是 `input`。",
|
"第一個節點固定是 `input`。",
|
||||||
].join("\n");
|
].join("\n");
|
||||||
|
|
||||||
const instructions = mapInstructions ? `${startHere}\n\n---\n\n${mapInstructions}` : startHere;
|
// 地圖那段永遠有東西可印:拿到 → 印地圖;沒拿到 → 印「沒拿到」,不是消失。
|
||||||
|
// stale 與「抓失敗」分開講,因為修法不同(前者要使用者重新連線,後者只是這次沒抓到)。
|
||||||
|
const mapSection =
|
||||||
|
mapInstructions ?? (identity.kind === "stale" ? MAP_STALE_NOTE : MAP_UNAVAILABLE_NOTE);
|
||||||
|
|
||||||
|
// 知識段排在 Arcrun 指路段之前:AI 最常被問的是「X 是什麼」,那題的正解是查庫,不是查零件。
|
||||||
|
return [KNOWLEDGE_FIRST, startHere, mapSection].join("\n\n---\n\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function handleMcpRequest(
|
||||||
|
request: Request,
|
||||||
|
env: Env,
|
||||||
|
orgNamespace: string,
|
||||||
|
partnerToken: string,
|
||||||
|
identity: KnowledgeIdentity,
|
||||||
|
): Promise<Response> {
|
||||||
|
const instructions = await buildServerInstructions(env, identity);
|
||||||
|
|
||||||
const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
const transport = new WebStandardStreamableHTTPServerTransport({ sessionIdGenerator: undefined });
|
||||||
const server = new McpServer(
|
const server = new McpServer(
|
||||||
|
|||||||
@@ -30,6 +30,79 @@ import { z } from "zod";
|
|||||||
import type { Env } from "../types.js";
|
import type { Env } from "../types.js";
|
||||||
import { kbdbFetch } from "../lib/kbdb-client.js";
|
import { kbdbFetch } from "../lib/kbdb-client.js";
|
||||||
import { errorResponse, successResponse } from "../lib/cypher-client.js";
|
import { errorResponse, successResponse } from "../lib/cypher-client.js";
|
||||||
|
import { staleIdentityError, type KnowledgeIdentity } from "../lib/portal-client.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 🔴 2026-08-13:**「讀不到」不准長得像「不存在」**(Arcrun#100/#109 同一族)。
|
||||||
|
*
|
||||||
|
* 實撞(總管在 leo21c/portal-login 連線上親跑,且已對照證實):
|
||||||
|
* `arcrun_get_skill('write_intent_workflow')` → 「skill "write_intent_workflow" **不存在**」
|
||||||
|
* 但 `kbdb_search` 撈得到 `page_name: "skill-write_intent_workflow"`、
|
||||||
|
* `entry_type: "agent-skill"`、`source: "installer-seed"`——**與本工具查的鍵逐字相符**。
|
||||||
|
* 真兇:下面的 `kbdbGetByPageName` 舊版寫 `if (!resp.ok) return null;`
|
||||||
|
* ⇒ **一個 HTTP 401 被逐字翻譯成「不存在」**。
|
||||||
|
*
|
||||||
|
* 401 從哪來(不是這支 code 的錯,但這支 code 把它講成了假話):`kbdb-client.ts` 只在
|
||||||
|
* `env.KBDB_INTERNAL_TOKEN` 存在時才掛 Authorization ⇒ 沒設就匿名送出 ⇒ KBDB 回 401。
|
||||||
|
* 而該實例的 `arcrun-mcp` 很可能根本沒拿到那把 token(安裝器的 secret 迴圈漏了它)。
|
||||||
|
* ⇒ **修 token 是部署面的事(leo 的手);這支 code 該做的是把話講對。**
|
||||||
|
*
|
||||||
|
* 為什麼講對很重要:instructions 叫 AI「第一步先讀 skill」。它照做、拿到「不存在」,
|
||||||
|
* 於是結論是「這裡沒有 skill」⇒ 去猜、去 grep repo、去上網——**那正是「AI 是瞎的」的機械過程**。
|
||||||
|
*
|
||||||
|
* 現在:非 2xx 一律拋 `KbdbAccessError`(帶真的 status),由 `kbdbFailure()` 講成它自己
|
||||||
|
* (401/403=讀不到;5xx/連不上=連不上),並把**還走得通的那條路**(`kbdb_search`)交給 AI。
|
||||||
|
* `not_found` 只保留給「KBDB 正常回應、但真的沒有這張卡」。
|
||||||
|
*/
|
||||||
|
class KbdbAccessError extends Error {
|
||||||
|
constructor(
|
||||||
|
readonly status: number,
|
||||||
|
readonly what: string,
|
||||||
|
readonly detail?: string,
|
||||||
|
) {
|
||||||
|
super(`KBDB ${what} HTTP ${status}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* KBDB 讀取失敗 → 誠實的錯誤回覆。
|
||||||
|
*
|
||||||
|
* `searchHint` 是「同一份內容還能從哪裡拿」——這批 registry 卡片(skill/example)本身就住在
|
||||||
|
* KBDB entries 裡,而 `kbdb_search` 走的是**另一條**路(登入身分走 portal 資料面),
|
||||||
|
* 這條 401 時那條常常還活著(2026-08-13 實測即如此)。所以這不是安慰話,是真的可行的下一步。
|
||||||
|
*/
|
||||||
|
function kbdbFailure(e: unknown, searchHint: string, identity: KnowledgeIdentity) {
|
||||||
|
const portalNote =
|
||||||
|
identity.kind === "portal"
|
||||||
|
? "你這條是帳密登入的連線,但 skill/example 這批工具目前仍走**服務內部憑據**(還沒接上登入身分)——" +
|
||||||
|
"所以它讀不到,不代表你的帳號讀不到。"
|
||||||
|
: "";
|
||||||
|
if (e instanceof KbdbAccessError) {
|
||||||
|
const unauthorized = e.status === 401 || e.status === 403;
|
||||||
|
return errorResponse(
|
||||||
|
unauthorized ? "kbdb_unauthorized" : "kbdb_unreachable",
|
||||||
|
(unauthorized
|
||||||
|
? `讀不到 KBDB(HTTP ${e.status}:這條連線的憑據被拒或根本沒帶)。`
|
||||||
|
: `讀不到 KBDB(HTTP ${e.status})。`) +
|
||||||
|
"🔴 **這是「讀不到」,不是「不存在」**——內容還在庫裡,只是這條路被擋住了。" +
|
||||||
|
(portalNote ? ` ${portalNote}` : ""),
|
||||||
|
[
|
||||||
|
searchHint,
|
||||||
|
"kbdb_get_map() 看這台實例有哪些庫(那條走得通就更確定是這批工具的路壞了,不是庫空了)",
|
||||||
|
"🔴 不准把這個錯誤回報成「找不到/沒有這個 skill」——請照實說「KBDB 這條路讀不到」",
|
||||||
|
"持續失敗:告訴 leo 這台實例的 arcrun-mcp 少了 secret KBDB_INTERNAL_TOKEN",
|
||||||
|
],
|
||||||
|
e.detail,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return errorResponse(
|
||||||
|
"kbdb_unreachable",
|
||||||
|
`讀不到 KBDB:${e instanceof Error ? e.message : String(e)}。` +
|
||||||
|
"🔴 **這是「讀不到」,不是「不存在」**。" +
|
||||||
|
(portalNote ? ` ${portalNote}` : ""),
|
||||||
|
[searchHint, "稍後重試", "🔴 不准把它回報成「沒有這個 skill/example」"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 基本盤 entries row(與舊 v3 block 欄位 1:1,差別只在 type→entry_type)
|
// 基本盤 entries row(與舊 v3 block 欄位 1:1,差別只在 type→entry_type)
|
||||||
interface KbdbBlock {
|
interface KbdbBlock {
|
||||||
@@ -45,14 +118,25 @@ interface KbdbBlock {
|
|||||||
|
|
||||||
async function kbdbList(env: Env, entryType: string, limit = 100): Promise<KbdbBlock[]> {
|
async function kbdbList(env: Env, entryType: string, limit = 100): Promise<KbdbBlock[]> {
|
||||||
const resp = await kbdbFetch(env, `/entries?entry_type=${encodeURIComponent(entryType)}&limit=${limit}`);
|
const resp = await kbdbFetch(env, `/entries?entry_type=${encodeURIComponent(entryType)}&limit=${limit}`);
|
||||||
if (!resp.ok) throw new Error(`KBDB list entry_type=${entryType} HTTP ${resp.status}`);
|
if (!resp.ok) {
|
||||||
|
throw new KbdbAccessError(resp.status, `list entry_type=${entryType}`, await resp.text().catch(() => ""));
|
||||||
|
}
|
||||||
const data = await resp.json<{ entries?: KbdbBlock[] }>();
|
const data = await resp.json<{ entries?: KbdbBlock[] }>();
|
||||||
return data.entries ?? [];
|
return data.entries ?? [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 依 page_name 取一張卡。
|
||||||
|
*
|
||||||
|
* 🔴 回 `null` **只代表「KBDB 好好回答了,而它說沒有這張卡」**。
|
||||||
|
* 讀不到(401/5xx/連不上)一律拋 `KbdbAccessError`——**絕不 return null**,
|
||||||
|
* 否則呼叫端會把它講成「不存在」(2026-08-13 的真實事故,見檔頭)。
|
||||||
|
*/
|
||||||
async function kbdbGetByPageName(env: Env, pageName: string): Promise<KbdbBlock | null> {
|
async function kbdbGetByPageName(env: Env, pageName: string): Promise<KbdbBlock | null> {
|
||||||
const resp = await kbdbFetch(env, `/entries?page_name=${encodeURIComponent(pageName)}&limit=1`);
|
const resp = await kbdbFetch(env, `/entries?page_name=${encodeURIComponent(pageName)}&limit=1`);
|
||||||
if (!resp.ok) return null;
|
if (!resp.ok) {
|
||||||
|
throw new KbdbAccessError(resp.status, `get page_name=${pageName}`, await resp.text().catch(() => ""));
|
||||||
|
}
|
||||||
const data = await resp.json<{ entries?: KbdbBlock[] }>();
|
const data = await resp.json<{ entries?: KbdbBlock[] }>();
|
||||||
return data.entries?.[0] ?? null;
|
return data.entries?.[0] ?? null;
|
||||||
}
|
}
|
||||||
@@ -67,7 +151,7 @@ function parseTags(tagsJson?: string): string[] {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerListSkills(server: McpServer, env: Env) {
|
export function registerListSkills(server: McpServer, env: Env, identity: KnowledgeIdentity) {
|
||||||
server.tool(
|
server.tool(
|
||||||
toolName("list_skills"),
|
toolName("list_skills"),
|
||||||
"列所有 agent-skill blocks(從 arcrun/registry/skills/ 同步進 KBDB)。每個 skill 是個 markdown playbook,描述 AI 面對 X 問題該怎麼想 + 該用哪個 example。回 [{slug, title, tags}]。call get_skill(slug) 拿完整內文。",
|
"列所有 agent-skill blocks(從 arcrun/registry/skills/ 同步進 KBDB)。每個 skill 是個 markdown playbook,描述 AI 面對 X 問題該怎麼想 + 該用哪個 example。回 [{slug, title, tags}]。call get_skill(slug) 拿完整內文。",
|
||||||
@@ -75,6 +159,7 @@ export function registerListSkills(server: McpServer, env: Env) {
|
|||||||
tag: z.string().optional().describe("optional 標籤過濾。如 'rag' / 'watcher' / 'debug'"),
|
tag: z.string().optional().describe("optional 標籤過濾。如 'rag' / 'watcher' / 'debug'"),
|
||||||
},
|
},
|
||||||
async ({ tag }) => {
|
async ({ tag }) => {
|
||||||
|
if (identity.kind === "stale") return staleIdentityError();
|
||||||
try {
|
try {
|
||||||
const blocks = await kbdbList(env, "agent-skill", 100);
|
const blocks = await kbdbList(env, "agent-skill", 100);
|
||||||
const skills = blocks
|
const skills = blocks
|
||||||
@@ -101,20 +186,19 @@ export function registerListSkills(server: McpServer, env: Env) {
|
|||||||
skills.length === 0
|
skills.length === 0
|
||||||
? "沒有 skill 命中。試 list_skills() 不帶 tag 看全部"
|
? "沒有 skill 命中。試 list_skills() 不帶 tag 看全部"
|
||||||
: "call arcrun_get_skill(slug) 拿單個 skill 完整 markdown",
|
: "call arcrun_get_skill(slug) 拿單個 skill 完整 markdown",
|
||||||
|
// 誠實:這裡回的是**這台實例被 seed 進去的那幾支**,不是「全世界的 skill 目錄」。
|
||||||
|
// 上面清單沒有的名字(例如 'INDEX')就是這台沒有——別照舊教材去猜一個 slug。
|
||||||
|
"🔴 只用上面清單裡真的有的 slug;清單沒有=這台實例沒 seed 進去,不要硬猜名字",
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return errorResponse(
|
return kbdbFailure(e, "改用 kbdb_search({ q: 'skill' }) 直接在知識庫裡找 skill 卡片(那條路走的是另一組憑據)", identity);
|
||||||
"fetch_failed",
|
|
||||||
e instanceof Error ? e.message : String(e),
|
|
||||||
["稍後重試", "若持續失敗,告訴 leo"],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerGetSkill(server: McpServer, env: Env) {
|
export function registerGetSkill(server: McpServer, env: Env, identity: KnowledgeIdentity) {
|
||||||
server.tool(
|
server.tool(
|
||||||
toolName("get_skill"),
|
toolName("get_skill"),
|
||||||
"拿單一 agent-skill 完整 markdown playbook。slug 從 list_skills 取得。",
|
"拿單一 agent-skill 完整 markdown playbook。slug 從 list_skills 取得。",
|
||||||
@@ -122,15 +206,19 @@ export function registerGetSkill(server: McpServer, env: Env) {
|
|||||||
slug: z.string().describe("skill slug,例如 'build_watcher_workflow' / 'rag_with_arcrun'"),
|
slug: z.string().describe("skill slug,例如 'build_watcher_workflow' / 'rag_with_arcrun'"),
|
||||||
},
|
},
|
||||||
async ({ slug }) => {
|
async ({ slug }) => {
|
||||||
|
if (identity.kind === "stale") return staleIdentityError();
|
||||||
try {
|
try {
|
||||||
const pageName = slug.startsWith("skill-") ? slug : `skill-${slug}`;
|
const pageName = slug.startsWith("skill-") ? slug : `skill-${slug}`;
|
||||||
const block = await kbdbGetByPageName(env, pageName);
|
const block = await kbdbGetByPageName(env, pageName);
|
||||||
if (!block) {
|
if (!block) {
|
||||||
|
// 走到這裡=KBDB **有正常回答**,而它說沒有這張卡(讀不到的情況上面已經拋出去了)。
|
||||||
return errorResponse(
|
return errorResponse(
|
||||||
"not_found",
|
"not_found",
|
||||||
`skill "${slug}" 不存在`,
|
`KBDB 正常回應,但沒有 page_name="${pageName}" 這張卡——這台實例沒有 seed 這支 skill。` +
|
||||||
|
"(不同實例 seed 的 skill 不一樣,別照舊教材假設某個名字一定在。)",
|
||||||
[
|
[
|
||||||
"call arcrun_list_skills() 看可用 slug",
|
"call arcrun_list_skills() 看**這台實例真的有**哪幾支",
|
||||||
|
`kbdb_search({ q: '${slug}' }) 看內容是不是被存成別的名字`,
|
||||||
"確認拼字正確(不需要 'skill-' prefix)",
|
"確認拼字正確(不需要 'skill-' prefix)",
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@@ -142,17 +230,17 @@ export function registerGetSkill(server: McpServer, env: Env) {
|
|||||||
tags: parseTags(block.tags_json),
|
tags: parseTags(block.tags_json),
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return errorResponse(
|
return kbdbFailure(
|
||||||
"fetch_failed",
|
e,
|
||||||
e instanceof Error ? e.message : String(e),
|
`改用 kbdb_search({ q: 'skill-${slug}' }) 撈同一張卡片(skill 就住在知識庫的 entries 裡,那條路走另一組憑據)`,
|
||||||
["稍後重試"],
|
identity,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerListExamples(server: McpServer, env: Env) {
|
export function registerListExamples(server: McpServer, env: Env, identity: KnowledgeIdentity) {
|
||||||
server.tool(
|
server.tool(
|
||||||
toolName("list_examples"),
|
toolName("list_examples"),
|
||||||
"列所有 workflow-example blocks(從 arcrun/registry/examples/ 同步進 KBDB)。每個 example 是可直接 push 的 workflow YAML 範本 + description。回 [{slug, tags}]。call get_example / search_examples 拿細節。",
|
"列所有 workflow-example blocks(從 arcrun/registry/examples/ 同步進 KBDB)。每個 example 是可直接 push 的 workflow YAML 範本 + description。回 [{slug, tags}]。call get_example / search_examples 拿細節。",
|
||||||
@@ -160,6 +248,7 @@ export function registerListExamples(server: McpServer, env: Env) {
|
|||||||
tag: z.string().optional().describe("optional 標籤過濾。如 'rag' / 'cron' / 'llm' / 'webhook'"),
|
tag: z.string().optional().describe("optional 標籤過濾。如 'rag' / 'cron' / 'llm' / 'webhook'"),
|
||||||
},
|
},
|
||||||
async ({ tag }) => {
|
async ({ tag }) => {
|
||||||
|
if (identity.kind === "stale") return staleIdentityError();
|
||||||
try {
|
try {
|
||||||
const blocks = await kbdbList(env, "workflow-example", 200);
|
const blocks = await kbdbList(env, "workflow-example", 200);
|
||||||
const examples = blocks
|
const examples = blocks
|
||||||
@@ -183,17 +272,13 @@ export function registerListExamples(server: McpServer, env: Env) {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return errorResponse(
|
return kbdbFailure(e, "改用 kbdb_search({ q: 'example' }) 直接在知識庫裡找 example 卡片(那條路走另一組憑據)", identity);
|
||||||
"fetch_failed",
|
|
||||||
e instanceof Error ? e.message : String(e),
|
|
||||||
["稍後重試"],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerGetExample(server: McpServer, env: Env) {
|
export function registerGetExample(server: McpServer, env: Env, identity: KnowledgeIdentity) {
|
||||||
server.tool(
|
server.tool(
|
||||||
toolName("get_example"),
|
toolName("get_example"),
|
||||||
"拿單一 workflow-example 完整 YAML + description。slug 從 list_examples / search_examples 取得。可直接拿 YAML 改成你自己的 → push。",
|
"拿單一 workflow-example 完整 YAML + description。slug 從 list_examples / search_examples 取得。可直接拿 YAML 改成你自己的 → push。",
|
||||||
@@ -201,16 +286,19 @@ export function registerGetExample(server: McpServer, env: Env) {
|
|||||||
slug: z.string().describe("example slug,例如 'rag-search-answer' / 'cron-watcher'"),
|
slug: z.string().describe("example slug,例如 'rag-search-answer' / 'cron-watcher'"),
|
||||||
},
|
},
|
||||||
async ({ slug }) => {
|
async ({ slug }) => {
|
||||||
|
if (identity.kind === "stale") return staleIdentityError();
|
||||||
try {
|
try {
|
||||||
const pageName = slug.startsWith("example-") ? slug : `example-${slug}`;
|
const pageName = slug.startsWith("example-") ? slug : `example-${slug}`;
|
||||||
const block = await kbdbGetByPageName(env, pageName);
|
const block = await kbdbGetByPageName(env, pageName);
|
||||||
if (!block) {
|
if (!block) {
|
||||||
|
// KBDB 好好回答了,而它說沒有這張卡(讀不到的情況已在 kbdbGetByPageName 拋出)。
|
||||||
return errorResponse(
|
return errorResponse(
|
||||||
"not_found",
|
"not_found",
|
||||||
`example "${slug}" 不存在`,
|
`KBDB 正常回應,但沒有 page_name="${pageName}" 這張卡——這台實例沒 seed 這個 example。`,
|
||||||
[
|
[
|
||||||
"call arcrun_list_examples() 看可用 slug",
|
"call arcrun_list_examples() 看**這台實例真的有**哪些 slug",
|
||||||
"或 arcrun_search_examples(use_case) 用自然語言找",
|
"或 arcrun_search_examples(use_case) 用關鍵字找",
|
||||||
|
`kbdb_search({ q: '${slug}' }) 看內容是不是被存成別的名字`,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -232,17 +320,17 @@ export function registerGetExample(server: McpServer, env: Env) {
|
|||||||
"看 description_md 了解設計意圖 / 改造方向",
|
"看 description_md 了解設計意圖 / 改造方向",
|
||||||
]);
|
]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return errorResponse(
|
return kbdbFailure(
|
||||||
"fetch_failed",
|
e,
|
||||||
e instanceof Error ? e.message : String(e),
|
`改用 kbdb_search({ q: 'example-${slug}' }) 撈同一張卡片(example 就住在知識庫的 entries 裡)`,
|
||||||
["稍後重試"],
|
identity,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerSearchExamples(server: McpServer, env: Env) {
|
export function registerSearchExamples(server: McpServer, env: Env, identity: KnowledgeIdentity) {
|
||||||
server.tool(
|
server.tool(
|
||||||
toolName("search_examples"),
|
toolName("search_examples"),
|
||||||
"用 use case 關鍵字搜 workflow examples,回最相關 N 個。" +
|
"用 use case 關鍵字搜 workflow examples,回最相關 N 個。" +
|
||||||
@@ -253,6 +341,7 @@ export function registerSearchExamples(server: McpServer, env: Env) {
|
|||||||
top_k: z.number().int().min(1).max(20).optional().describe("回幾個結果(預設 5)"),
|
top_k: z.number().int().min(1).max(20).optional().describe("回幾個結果(預設 5)"),
|
||||||
},
|
},
|
||||||
async ({ query, top_k }) => {
|
async ({ query, top_k }) => {
|
||||||
|
if (identity.kind === "stale") return staleIdentityError();
|
||||||
try {
|
try {
|
||||||
const k = top_k ?? 5;
|
const k = top_k ?? 5;
|
||||||
const q = query.trim();
|
const q = query.trim();
|
||||||
@@ -309,20 +398,16 @@ export function registerSearchExamples(server: McpServer, env: Env) {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return errorResponse(
|
return kbdbFailure(e, `改用 kbdb_search({ q: '${query.trim()}' }) 直接查知識庫(那條路走另一組憑據)`, identity);
|
||||||
"internal_error",
|
|
||||||
e instanceof Error ? e.message : String(e),
|
|
||||||
["重試一次"],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function registerAllSkillExampleTools(server: McpServer, env: Env) {
|
export function registerAllSkillExampleTools(server: McpServer, env: Env, identity: KnowledgeIdentity) {
|
||||||
registerListSkills(server, env);
|
registerListSkills(server, env, identity);
|
||||||
registerGetSkill(server, env);
|
registerGetSkill(server, env, identity);
|
||||||
registerListExamples(server, env);
|
registerListExamples(server, env, identity);
|
||||||
registerGetExample(server, env);
|
registerGetExample(server, env, identity);
|
||||||
registerSearchExamples(server, env);
|
registerSearchExamples(server, env, identity);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,11 @@ export function registerAllTools(
|
|||||||
registerAllWorkflowCrudTools(server, env);
|
registerAllWorkflowCrudTools(server, env);
|
||||||
// LI SDD M3.2: skills + examples lookup(KBDB-backed)
|
// LI SDD M3.2: skills + examples lookup(KBDB-backed)
|
||||||
// 走 sync-registry-to-kbdb.py 把 registry/{skills,examples} 同步進 KBDB
|
// 走 sync-registry-to-kbdb.py 把 registry/{skills,examples} 同步進 KBDB
|
||||||
registerAllSkillExampleTools(server, env);
|
// 2026-08-13:吃 identity 只為了「把話講對」——舊 token 誠實回 identity_missing,
|
||||||
|
// 且 401 時能告訴登入者「是這批工具還走服務憑據,不是你的帳號讀不到」。
|
||||||
|
// ⚠️ 這批**尚未**改走 portal 資料面(portal 沒有 by-entry_type 的 listing 端點;
|
||||||
|
// 要接得補 API,不是在這層拼裝——rule 07 §3.1)。見該檔檔頭。
|
||||||
|
registerAllSkillExampleTools(server, env, identity);
|
||||||
// kbdb-base §7.5.i: recipe 公庫/私庫工具(與 CLI 六能力對齊,rule 07 §5 MCP 不落後)
|
// kbdb-base §7.5.i: recipe 公庫/私庫工具(與 CLI 六能力對齊,rule 07 §5 MCP 不落後)
|
||||||
registerAllRecipeTools(server, env);
|
registerAllRecipeTools(server, env);
|
||||||
// kbdb-base Phase 9.1: KBDB 資料層薄殼(template/record/query/search,HANDOFF §2)
|
// kbdb-base Phase 9.1: KBDB 資料層薄殼(template/record/query/search,HANDOFF §2)
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
/**
|
||||||
|
* server instructions(`initialize` 時送出的那份,AI **沒辦法自己再要一次**)。
|
||||||
|
*
|
||||||
|
* 這份測試守的是 2026-08-13 leo 那句話:
|
||||||
|
* 「它應該是**你的知識來源**⋯⋯**沒有它你是瞎的**。」
|
||||||
|
*
|
||||||
|
* 病灶(實測):instructions 裡唯一提到「這裡有知識可查」的,是**選配的**【藏書地圖】段
|
||||||
|
* (1500ms 逾時、失敗回 null、無聲消失)。一條 portal 連線就沒收到它 ⇒ 那個 session
|
||||||
|
* 為了回答「Arcrun 是什麼」去讀了 682 行原始碼,而 `kbdb_search` 當下回得出 33 筆真答案。
|
||||||
|
*
|
||||||
|
* 所以本檔的驗收判準只有一句:
|
||||||
|
* **不管地圖抓不抓得到、身分是哪一種,讀完這份 instructions 的下一個動作都必須是「去查知識庫」。**
|
||||||
|
*/
|
||||||
|
import { describe, it, expect, beforeEach } from "vitest";
|
||||||
|
import { buildServerInstructions } from "../../src/mcp-handler.js";
|
||||||
|
import { __resetLibraryMapInstructionsCacheForTests } from "../../src/lib/library-map.js";
|
||||||
|
import type { Env } from "../../src/types.js";
|
||||||
|
import type { KnowledgeIdentity } from "../../src/lib/portal-client.js";
|
||||||
|
|
||||||
|
const SERVICE: KnowledgeIdentity = { kind: "service" };
|
||||||
|
const STALE: KnowledgeIdentity = { kind: "stale" };
|
||||||
|
|
||||||
|
/** 假 KBDB service binding(服務級憑據路徑)。 */
|
||||||
|
function makeEnv(respond: () => Response | Promise<Response>): Env {
|
||||||
|
return { KBDB: { fetch: async () => respond() } } as unknown as Env;
|
||||||
|
}
|
||||||
|
|
||||||
|
const MAP_OK = () =>
|
||||||
|
new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
libraries: [
|
||||||
|
{ library: "kb", narrative: "leo 的知識庫主庫", top_entities: ["Arcrun"], triplet_count: 1854 },
|
||||||
|
],
|
||||||
|
count: 1,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 「一個什麼都不知道的 AI 讀完,下一個動作會是什麼」的機械化判準。
|
||||||
|
* 三份 instructions 都必須通過同一組斷言——通不過就是還沒修好。
|
||||||
|
*/
|
||||||
|
function expectPointsAtKnowledge(text: string) {
|
||||||
|
// ① 明講這條連線後面有主人的知識庫
|
||||||
|
expect(text).toContain("知識庫");
|
||||||
|
// ② 指名工具與呼叫法(AI 不必猜工具名)
|
||||||
|
expect(text).toContain("kbdb_search");
|
||||||
|
expect(text).toContain("Arcrun 是什麼");
|
||||||
|
// ③ 明確排除三條錯路
|
||||||
|
expect(text).toContain("不是 grep 原始碼");
|
||||||
|
expect(text).toContain("不是上網搜");
|
||||||
|
expect(text).toContain("不是回答「我不知道」");
|
||||||
|
// ④ 「沒查到」與「沒有」不可以混為一談
|
||||||
|
expect(text).toContain("那是**讀不到**,不是**不存在**");
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("buildServerInstructions — 三種情境都必須指向知識庫", () => {
|
||||||
|
beforeEach(() => __resetLibraryMapInstructionsCacheForTests());
|
||||||
|
|
||||||
|
it("① 地圖抓得到:地圖照舊注入,且知識段仍在", async () => {
|
||||||
|
const text = await buildServerInstructions(makeEnv(MAP_OK), SERVICE);
|
||||||
|
expectPointsAtKnowledge(text);
|
||||||
|
expect(text).toContain("【藏書地圖】");
|
||||||
|
expect(text).toContain("kb:leo 的知識庫主庫");
|
||||||
|
// 地圖成功時不該同時出現「沒取到」的話
|
||||||
|
expect(text).not.toContain("【藏書地圖:這次沒取到】");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("② 地圖抓不到(HTTP 500):明講「沒取到」,不靜默、也不等於沒有知識", async () => {
|
||||||
|
const text = await buildServerInstructions(makeEnv(() => new Response("boom", { status: 500 })), SERVICE);
|
||||||
|
expectPointsAtKnowledge(text);
|
||||||
|
expect(text).toContain("【藏書地圖:這次沒取到】");
|
||||||
|
expect(text).toContain("這是「地圖沒拿到」,不是「這裡沒有知識」");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("② 地圖逾時/binding 爆炸:同樣明講,不擋連線(不 throw)", async () => {
|
||||||
|
const env = {
|
||||||
|
KBDB: {
|
||||||
|
fetch: async () => {
|
||||||
|
throw new Error("kbdb down");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as unknown as Env;
|
||||||
|
const text = await buildServerInstructions(env, SERVICE);
|
||||||
|
expectPointsAtKnowledge(text);
|
||||||
|
expect(text).toContain("【藏書地圖:這次沒取到】");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("② 地圖回空清單:也算沒取到,不可以長得像「這裡沒有知識」", async () => {
|
||||||
|
const text = await buildServerInstructions(
|
||||||
|
makeEnv(() => new Response(JSON.stringify({ success: true, libraries: [], count: 0 }))),
|
||||||
|
SERVICE,
|
||||||
|
);
|
||||||
|
expect(text).toContain("【藏書地圖:這次沒取到】");
|
||||||
|
expect(text).toContain("不是「這裡沒有知識」");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("③ 舊 token(stale):講成身分問題+給可執行的修法,仍指向知識庫", async () => {
|
||||||
|
const text = await buildServerInstructions(makeEnv(MAP_OK), STALE);
|
||||||
|
expectPointsAtKnowledge(text);
|
||||||
|
expect(text).toContain("舊版簽發的 token");
|
||||||
|
expect(text).toContain("identity_missing");
|
||||||
|
expect(text).toContain("重新連線");
|
||||||
|
// 🔴 不可以講成「知識庫是空的」
|
||||||
|
expect(text).toContain("這不代表知識庫是空的");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("stale 不去打 KBDB(地圖本身就是情報,舊 token 不給)", async () => {
|
||||||
|
let called = 0;
|
||||||
|
const env = {
|
||||||
|
KBDB: {
|
||||||
|
fetch: async () => {
|
||||||
|
called += 1;
|
||||||
|
return MAP_OK();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as unknown as Env;
|
||||||
|
await buildServerInstructions(env, STALE);
|
||||||
|
expect(called).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Arcrun 指路段照舊存在(知識段是新增的,不是取代)", async () => {
|
||||||
|
const text = await buildServerInstructions(makeEnv(MAP_OK), SERVICE);
|
||||||
|
expect(text).toContain("# Arcrun — 你已經配備了這套工具,別上網找");
|
||||||
|
expect(text).toContain("【先讀這裡】");
|
||||||
|
// 步驟 4 不再指名一個可能沒被 seed 的 slug(leo21c 實測連 INDEX 都沒有)
|
||||||
|
expect(text).toContain("arcrun_list_skills()");
|
||||||
|
expect(text).not.toContain("arcrun_get_skill('INDEX')");
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/**
|
||||||
|
* skill/example 工具的**誠實性**測試(2026-08-13,Arcrun#100/#109 同一族)。
|
||||||
|
*
|
||||||
|
* 真實事故:`arcrun_get_skill('write_intent_workflow')` 回「skill ... **不存在**」,
|
||||||
|
* 但同一台實例的 `kbdb_search` 撈得到 `page_name: "skill-write_intent_workflow"`
|
||||||
|
*(`entry_type: agent-skill`,`source: installer-seed`)——**查的鍵逐字相符**。
|
||||||
|
* 真兇是 `if (!resp.ok) return null;`:一個 HTTP 401 被翻譯成「不存在」。
|
||||||
|
*
|
||||||
|
* 這個謊有實害:instructions 叫 AI 第一步先讀 skill,它照做、收到「不存在」,
|
||||||
|
* 於是結論「這裡沒有 skill」⇒ 去猜/grep repo/上網。
|
||||||
|
*
|
||||||
|
* 判準:**讀不到(401/403/5xx/連不上)與不存在(KBDB 正常回應但沒這張卡)必須長得不一樣**,
|
||||||
|
* 且讀不到時要交出還走得通的下一步(`kbdb_search`)。
|
||||||
|
*/
|
||||||
|
import { describe, it, expect } from "vitest";
|
||||||
|
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||||
|
import type { Env } from "../../../src/types.js";
|
||||||
|
import {
|
||||||
|
registerGetSkill,
|
||||||
|
registerListSkills,
|
||||||
|
registerGetExample,
|
||||||
|
} from "../../../src/tools/arcrun_skills_examples.js";
|
||||||
|
import type { KnowledgeIdentity } from "../../../src/lib/portal-client.js";
|
||||||
|
|
||||||
|
const SERVICE: KnowledgeIdentity = { kind: "service" };
|
||||||
|
const PORTAL: KnowledgeIdentity = {
|
||||||
|
kind: "portal",
|
||||||
|
portal: { session: "sess-abc", display_name: "Leo", role: "admin", libraries: ["*"] },
|
||||||
|
};
|
||||||
|
const STALE: KnowledgeIdentity = { kind: "stale" };
|
||||||
|
|
||||||
|
type ToolHandler = (args: Record<string, unknown>) => Promise<{
|
||||||
|
content: { type: string; text: string }[];
|
||||||
|
isError?: boolean;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
function fakeServer() {
|
||||||
|
const tools = new Map<string, ToolHandler>();
|
||||||
|
const server = {
|
||||||
|
tool: (name: string, _desc: string, _schema: unknown, handler: ToolHandler) => {
|
||||||
|
tools.set(name, handler);
|
||||||
|
},
|
||||||
|
} as unknown as McpServer;
|
||||||
|
return { server, tools };
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeEnv(respond: (url: string) => Response): Env {
|
||||||
|
return {
|
||||||
|
KBDB_INTERNAL_TOKEN: "",
|
||||||
|
KBDB: { fetch: async (url: string) => respond(url) },
|
||||||
|
} as unknown as Env;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parse = (res: { content: { text: string }[] }) => JSON.parse(res.content[0].text);
|
||||||
|
|
||||||
|
describe("arcrun_get_skill — 401 不准講成「不存在」", () => {
|
||||||
|
it("KBDB 回 401 → kbdb_unauthorized,訊息明說「讀不到不是不存在」並給 kbdb_search 這條路", async () => {
|
||||||
|
const { server, tools } = fakeServer();
|
||||||
|
registerGetSkill(server, makeEnv(() => new Response("unauthorized", { status: 401 })), SERVICE);
|
||||||
|
const res = await tools.get("arcrun_get_skill")!({ slug: "write_intent_workflow" });
|
||||||
|
|
||||||
|
expect(res.isError).toBe(true);
|
||||||
|
const body = parse(res);
|
||||||
|
expect(body.error_code).toBe("kbdb_unauthorized");
|
||||||
|
// 🔴 這句是本次事故的核心:不可以再出現舊版那句「skill "x" 不存在」的結論
|
||||||
|
expect(body.human_message).not.toMatch(/skill "[^"]*" 不存在/);
|
||||||
|
expect(body.human_message).toContain("這是「讀不到」,不是「不存在」");
|
||||||
|
expect(body.human_message).toContain("讀不到");
|
||||||
|
expect(body.human_message).toContain("HTTP 401");
|
||||||
|
// 還走得通的那條路要交到 AI 手上(實測 kbdb_search 撈得到同一張卡)
|
||||||
|
expect(body.next_actions.join("\n")).toContain("kbdb_search({ q: 'skill-write_intent_workflow' })");
|
||||||
|
// 並明白禁止它改口
|
||||||
|
expect(body.next_actions.join("\n")).toContain("不准把這個錯誤回報成");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("以帳密登入的連線收到 401 → additionally 說清楚「不是你的帳號讀不到」", async () => {
|
||||||
|
const { server, tools } = fakeServer();
|
||||||
|
registerGetSkill(server, makeEnv(() => new Response("unauthorized", { status: 401 })), PORTAL);
|
||||||
|
const body = parse(await tools.get("arcrun_get_skill")!({ slug: "write_recipe" }));
|
||||||
|
expect(body.human_message).toContain("服務內部憑據");
|
||||||
|
expect(body.human_message).toContain("不代表你的帳號讀不到");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("KBDB 5xx → kbdb_unreachable(連不上,也不是不存在)", async () => {
|
||||||
|
const { server, tools } = fakeServer();
|
||||||
|
registerGetSkill(server, makeEnv(() => new Response("boom", { status: 503 })), SERVICE);
|
||||||
|
const body = parse(await tools.get("arcrun_get_skill")!({ slug: "whatever" }));
|
||||||
|
expect(body.error_code).toBe("kbdb_unreachable");
|
||||||
|
expect(body.human_message).toContain("HTTP 503");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("binding 直接爆炸 → 也回 kbdb_unreachable,不吞成 not_found", async () => {
|
||||||
|
const { server, tools } = fakeServer();
|
||||||
|
const env = {
|
||||||
|
KBDB: {
|
||||||
|
fetch: async () => {
|
||||||
|
throw new Error("kbdb down");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as unknown as Env;
|
||||||
|
registerGetSkill(server, env, SERVICE);
|
||||||
|
const body = parse(await tools.get("arcrun_get_skill")!({ slug: "x" }));
|
||||||
|
expect(body.error_code).toBe("kbdb_unreachable");
|
||||||
|
expect(body.human_message).toContain("kbdb down");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("KBDB 正常回應但真的沒這張卡 → not_found,且說明是「這台實例沒 seed」不是全世界沒有", async () => {
|
||||||
|
const { server, tools } = fakeServer();
|
||||||
|
registerGetSkill(
|
||||||
|
server,
|
||||||
|
makeEnv(() => new Response(JSON.stringify({ entries: [] }))),
|
||||||
|
SERVICE,
|
||||||
|
);
|
||||||
|
const body = parse(await tools.get("arcrun_get_skill")!({ slug: "INDEX" }));
|
||||||
|
expect(body.error_code).toBe("not_found");
|
||||||
|
expect(body.human_message).toContain("KBDB 正常回應");
|
||||||
|
expect(body.human_message).toContain("skill-INDEX");
|
||||||
|
expect(body.next_actions.join("\n")).toContain("arcrun_list_skills()");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("卡片真的在 → 照舊回內容(沒改壞正路)", async () => {
|
||||||
|
const { server, tools } = fakeServer();
|
||||||
|
let seen = "";
|
||||||
|
registerGetSkill(
|
||||||
|
server,
|
||||||
|
makeEnv((url) => {
|
||||||
|
seen = url;
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
entries: [{ id: "e1", page_name: "skill-write_intent_workflow", content: "# 意圖工作流", tags_json: '["skill:core"]' }],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
SERVICE,
|
||||||
|
);
|
||||||
|
const body = parse(await tools.get("arcrun_get_skill")!({ slug: "write_intent_workflow" }));
|
||||||
|
expect(body.ok).toBe(true);
|
||||||
|
expect(body.data.content).toBe("# 意圖工作流");
|
||||||
|
expect(seen).toContain("page_name=skill-write_intent_workflow");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("舊 token(stale)→ identity_missing(誠實要求重新連線,不謊稱找不到)", async () => {
|
||||||
|
const { server, tools } = fakeServer();
|
||||||
|
registerGetSkill(server, makeEnv(() => new Response("{}")), STALE);
|
||||||
|
const body = parse(await tools.get("arcrun_get_skill")!({ slug: "x" }));
|
||||||
|
expect(body.error_code).toBe("identity_missing");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("arcrun_list_skills / arcrun_get_example — 同一條規則", () => {
|
||||||
|
it("list_skills 撞 401 → kbdb_unauthorized(舊版是 fetch_failed 一句技術話)", async () => {
|
||||||
|
const { server, tools } = fakeServer();
|
||||||
|
registerListSkills(server, makeEnv(() => new Response("nope", { status: 401 })), SERVICE);
|
||||||
|
const body = parse(await tools.get("arcrun_list_skills")!({}));
|
||||||
|
expect(body.error_code).toBe("kbdb_unauthorized");
|
||||||
|
expect(body.next_actions.join("\n")).toContain("kbdb_search");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("list_skills 成功時提醒「只用清單裡真的有的 slug」(別再猜 INDEX)", async () => {
|
||||||
|
const { server, tools } = fakeServer();
|
||||||
|
registerListSkills(
|
||||||
|
server,
|
||||||
|
makeEnv(() =>
|
||||||
|
new Response(
|
||||||
|
JSON.stringify({
|
||||||
|
entries: [{ id: "e1", page_name: "skill-write_recipe", content: "x", tags_json: "[]" }],
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SERVICE,
|
||||||
|
);
|
||||||
|
const body = parse(await tools.get("arcrun_list_skills")!({}));
|
||||||
|
expect(body.ok).toBe(true);
|
||||||
|
expect(body.data.count).toBe(1);
|
||||||
|
expect(body.hints.join("\n")).toContain("不要硬猜名字");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("get_example 撞 401 → 同樣是讀不到,不是「example 不存在」", async () => {
|
||||||
|
const { server, tools } = fakeServer();
|
||||||
|
registerGetExample(server, makeEnv(() => new Response("nope", { status: 401 })), SERVICE);
|
||||||
|
const body = parse(await tools.get("arcrun_get_example")!({ slug: "rag-search-answer" }));
|
||||||
|
expect(body.error_code).toBe("kbdb_unauthorized");
|
||||||
|
expect(body.human_message).not.toMatch(/example "[^"]*" 不存在/);
|
||||||
|
expect(body.next_actions.join("\n")).toContain("kbdb_search({ q: 'example-rag-search-answer' })");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user