@ghostpaw/questlog 0.9.1 → 0.10.1
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/README.md +180 -18
- package/SKILL.md +161 -32
- package/dist/{chunk-3Y5F3SMB.js → chunk-55JAQORX.js} +245 -665
- package/dist/{questlog_skill_spec-LOQ2FNOF.js → chunk-D2W5MYNO.js} +9 -54
- package/dist/chunk-GVD6A3RR.js +108 -0
- package/dist/chunk-IL2QRKZL.js +14 -0
- package/dist/chunk-N7V7VUI5.js +982 -0
- package/dist/chunk-QYSIJ2YN.js +47 -0
- package/dist/{chunk-3B6FMHZU.js → chunk-ZHTX64KD.js} +24 -0
- package/dist/cli.js +1 -1
- package/dist/{cli_main-ELAKW6KW.js → cli_main-7AIEZV75.js} +136 -120
- package/dist/index.cjs +2046 -1393
- package/dist/index.d.cts +142 -11
- package/dist/index.d.ts +142 -11
- package/dist/index.js +2046 -1394
- package/dist/mcp-server.js +959 -386
- package/dist/mcp.cjs +810 -328
- package/dist/mcp.js +811 -329
- package/dist/open_questlog_db-LAEH7UDM.js +2 -0
- package/dist/questlog_skill_spec-HEW2B6EB.js +76 -0
- package/dist/run_hook_command-C42BQC36.js +95 -0
- package/dist/{serve_questlog_mcp-QSHMNNFT.js → serve_questlog_mcp-W3R63HW4.js} +14 -4
- package/dist/{skill_markdown-7HCMSJYR.js → skill_markdown-HXYQ3K6U.js} +34 -6
- package/package.json +6 -3
- package/skills/questlog/SKILL.md +161 -32
- package/skills/questlog/references/webhooks.md +91 -0
- package/dist/chunk-KRQDV4KF.js +0 -58
- package/dist/open_questlog_db-SH3DAU4C.js +0 -2
package/dist/mcp-server.js
CHANGED
|
@@ -1,18 +1,79 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { mkdirSync } from 'node:fs';
|
|
3
|
-
import { dirname, join } from 'node:path';
|
|
3
|
+
import { basename, extname, dirname, join } from 'node:path';
|
|
4
|
+
import { randomBytes, createHmac } from 'node:crypto';
|
|
4
5
|
import { homedir } from 'node:os';
|
|
5
6
|
|
|
6
7
|
var __defProp = Object.defineProperty;
|
|
7
8
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
8
|
-
var __esm = (fn, res) => function __init() {
|
|
9
|
-
|
|
9
|
+
var __esm = (fn, res, err) => function __init() {
|
|
10
|
+
if (err) throw err[0];
|
|
11
|
+
try {
|
|
12
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
13
|
+
} catch (e) {
|
|
14
|
+
throw err = [e], e;
|
|
15
|
+
}
|
|
10
16
|
};
|
|
11
17
|
var __export = (target, all) => {
|
|
12
18
|
for (var name in all)
|
|
13
19
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
14
20
|
};
|
|
15
21
|
|
|
22
|
+
// src/surfaces/schema_actions.ts
|
|
23
|
+
function registerSchemaActions(schema, actions) {
|
|
24
|
+
requirements.set(schema, actions);
|
|
25
|
+
}
|
|
26
|
+
function requiredForAction(schema, action) {
|
|
27
|
+
return requirements.get(schema)?.[String(action)] ?? [];
|
|
28
|
+
}
|
|
29
|
+
var requirements;
|
|
30
|
+
var init_schema_actions = __esm({
|
|
31
|
+
"src/surfaces/schema_actions.ts"() {
|
|
32
|
+
requirements = /* @__PURE__ */ new WeakMap();
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// src/surfaces/schema_examples.ts
|
|
37
|
+
function minimalInput(schema, action, name = "") {
|
|
38
|
+
const constant = "const" in schema ? schema.const : void 0;
|
|
39
|
+
if (constant !== void 0) return constant;
|
|
40
|
+
if (schema.enum)
|
|
41
|
+
return action !== void 0 && schema.enum.includes(action) ? action : schema.enum[0];
|
|
42
|
+
const branch = schema.oneOf?.[0];
|
|
43
|
+
if (schema.type === "object" || schema.properties) {
|
|
44
|
+
action ??= schema.properties?.action?.enum?.[0];
|
|
45
|
+
const properties = { ...schema.properties, ...branch?.properties };
|
|
46
|
+
return Object.fromEntries(
|
|
47
|
+
[
|
|
48
|
+
.../* @__PURE__ */ new Set([
|
|
49
|
+
...schema.required ?? [],
|
|
50
|
+
...requiredForAction(schema, action),
|
|
51
|
+
...schema.properties?.outcome && ["finish", "abandon", "abandon_and_spawn_followups"].includes(String(action)) ? ["outcome"] : [],
|
|
52
|
+
...branch?.required ?? []
|
|
53
|
+
])
|
|
54
|
+
].map((key) => [
|
|
55
|
+
key,
|
|
56
|
+
minimalInput(properties[key] ?? {}, key === "action" ? action : void 0, key)
|
|
57
|
+
])
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
if (branch) return minimalInput(branch, action, name);
|
|
61
|
+
if (schema.type === "array") return [minimalInput(schema.items ?? {}, void 0, name)];
|
|
62
|
+
if (schema.type === "integer" || schema.type === "number")
|
|
63
|
+
return name === "anchorAt" ? 18e11 : name === "toQuestId" ? 2 : 1;
|
|
64
|
+
if (schema.type === "boolean") return false;
|
|
65
|
+
if (schema.type === "null") return null;
|
|
66
|
+
if (name === "rrule") return "FREQ=WEEKLY";
|
|
67
|
+
if (name === "title") return "Review release notes";
|
|
68
|
+
if (name === "objective") return "Check changes against the release";
|
|
69
|
+
return "release";
|
|
70
|
+
}
|
|
71
|
+
var init_schema_examples = __esm({
|
|
72
|
+
"src/surfaces/schema_examples.ts"() {
|
|
73
|
+
init_schema_actions();
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
16
77
|
// src/surfaces/surface_types.ts
|
|
17
78
|
function isErrorResult(result) {
|
|
18
79
|
return typeof result === "object" && result !== null && "ok" in result && result.ok === false;
|
|
@@ -46,11 +107,22 @@ function validateInput(schema, input, path = "$") {
|
|
|
46
107
|
}
|
|
47
108
|
if (schema.enum && !schema.enum.some((value) => JSON.stringify(value) === JSON.stringify(input)))
|
|
48
109
|
errors.push(`${path}: expected one of ${JSON.stringify(schema.enum)}`);
|
|
49
|
-
|
|
50
|
-
|
|
110
|
+
const constant = "const" in schema ? schema.const : void 0;
|
|
111
|
+
if (constant !== void 0 && constant !== input)
|
|
112
|
+
errors.push(`${path}: expected ${JSON.stringify(constant)}`);
|
|
113
|
+
if (schema.oneOf) {
|
|
114
|
+
const branches = schema.oneOf.map((branch) => validateInput(branch, input, path));
|
|
115
|
+
if (!branches.some((errors2) => errors2.length === 0)) {
|
|
116
|
+
errors.push(
|
|
117
|
+
...branches.sort((a, b) => a.length - b.length)[0] ?? [
|
|
118
|
+
`${path}: must match at least one schema branch`
|
|
119
|
+
]
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
51
123
|
if (typeof input === "object" && input !== null && !Array.isArray(input)) {
|
|
52
124
|
const record = input;
|
|
53
|
-
for (const name of schema.required ?? [])
|
|
125
|
+
for (const name of [...schema.required ?? [], ...requiredForAction(schema, record.action)])
|
|
54
126
|
if (!Object.hasOwn(record, name)) errors.push(`${path}.${name}: required`);
|
|
55
127
|
for (const [name, property] of Object.entries(schema.properties ?? {}))
|
|
56
128
|
if (Object.hasOwn(record, name))
|
|
@@ -64,6 +136,7 @@ function validateInput(schema, input, path = "$") {
|
|
|
64
136
|
}
|
|
65
137
|
var init_validate_input = __esm({
|
|
66
138
|
"src/surfaces/validate_input.ts"() {
|
|
139
|
+
init_schema_actions();
|
|
67
140
|
}
|
|
68
141
|
});
|
|
69
142
|
|
|
@@ -158,7 +231,15 @@ Not for: ${tool.whenNotToUse}` : ""),
|
|
|
158
231
|
const input = params.arguments === void 0 ? {} : params.arguments;
|
|
159
232
|
const errors = validateInput(tool.inputSchema, input);
|
|
160
233
|
if (errors.length)
|
|
161
|
-
return result({
|
|
234
|
+
return result({
|
|
235
|
+
content: [
|
|
236
|
+
{
|
|
237
|
+
type: "text",
|
|
238
|
+
text: `${errors.join("\n")}; pass ${JSON.stringify(minimalInput(tool.inputSchema, input?.action))}`
|
|
239
|
+
}
|
|
240
|
+
],
|
|
241
|
+
isError: true
|
|
242
|
+
});
|
|
162
243
|
try {
|
|
163
244
|
context ??= Promise.resolve().then(options.context);
|
|
164
245
|
const value = await tool.handler(await context, input);
|
|
@@ -197,6 +278,7 @@ Not for: ${tool.whenNotToUse}` : ""),
|
|
|
197
278
|
var MCP_LATEST_PROTOCOL_VERSION, MCP_SUPPORTED_PROTOCOL_VERSIONS;
|
|
198
279
|
var init_mcp_server = __esm({
|
|
199
280
|
"src/surfaces/mcp_server.ts"() {
|
|
281
|
+
init_schema_examples();
|
|
200
282
|
init_surface_types();
|
|
201
283
|
init_validate_input();
|
|
202
284
|
MCP_LATEST_PROTOCOL_VERSION = "2026-07-28";
|
|
@@ -242,6 +324,33 @@ var init_mcp_stdio = __esm({
|
|
|
242
324
|
}
|
|
243
325
|
});
|
|
244
326
|
|
|
327
|
+
// src/hooks/init_hook_tables.ts
|
|
328
|
+
function initHookTables(db) {
|
|
329
|
+
db.exec(`
|
|
330
|
+
CREATE TABLE IF NOT EXISTS hook_targets (
|
|
331
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT NOT NULL, secret TEXT NOT NULL,
|
|
332
|
+
tag TEXT, events TEXT NOT NULL, min_gap INTEGER NOT NULL, created_at INTEGER NOT NULL,
|
|
333
|
+
last_wake INTEGER, lease_until INTEGER, claim TEXT);
|
|
334
|
+
CREATE TABLE IF NOT EXISTS hook_events (
|
|
335
|
+
id TEXT PRIMARY KEY, occurred_at INTEGER NOT NULL, event TEXT NOT NULL);
|
|
336
|
+
CREATE TABLE IF NOT EXISTS hook_assignments (
|
|
337
|
+
target_id INTEGER NOT NULL REFERENCES hook_targets(id) ON DELETE CASCADE,
|
|
338
|
+
event_id TEXT NOT NULL REFERENCES hook_events(id), delivery_id TEXT,
|
|
339
|
+
PRIMARY KEY(target_id, event_id));
|
|
340
|
+
CREATE TABLE IF NOT EXISTS hook_deliveries (
|
|
341
|
+
id TEXT PRIMARY KEY, target_id INTEGER NOT NULL REFERENCES hook_targets(id) ON DELETE CASCADE,
|
|
342
|
+
events TEXT NOT NULL, status TEXT NOT NULL, attempts INTEGER NOT NULL DEFAULT 0,
|
|
343
|
+
last_attempt_at INTEGER, http_status INTEGER, error_class TEXT,
|
|
344
|
+
next_attempt_at INTEGER NOT NULL);
|
|
345
|
+
CREATE INDEX IF NOT EXISTS idx_hook_deliveries_target ON hook_deliveries(target_id, status, next_attempt_at);
|
|
346
|
+
CREATE TABLE IF NOT EXISTS hook_settings (id INTEGER PRIMARY KEY CHECK(id = 1), label TEXT NOT NULL);
|
|
347
|
+
`);
|
|
348
|
+
}
|
|
349
|
+
var init_init_hook_tables = __esm({
|
|
350
|
+
"src/hooks/init_hook_tables.ts"() {
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
|
|
245
354
|
// src/questlines/init_questline_tables.ts
|
|
246
355
|
function initQuestlineTables(db) {
|
|
247
356
|
db.exec(`
|
|
@@ -626,9 +735,11 @@ function initQuestlogTables(db) {
|
|
|
626
735
|
initTagTables(db);
|
|
627
736
|
initQuestSearch(db);
|
|
628
737
|
initRumorSearch(db);
|
|
738
|
+
initHookTables(db);
|
|
629
739
|
}
|
|
630
740
|
var init_init_questlog_tables = __esm({
|
|
631
741
|
"src/init_questlog_tables.ts"() {
|
|
742
|
+
init_init_hook_tables();
|
|
632
743
|
init_init_questline_tables();
|
|
633
744
|
init_init_quest_search();
|
|
634
745
|
init_init_quest_tables();
|
|
@@ -677,7 +788,7 @@ var init_open_questlog_db = __esm({
|
|
|
677
788
|
var questlogVersion;
|
|
678
789
|
var init_version = __esm({
|
|
679
790
|
"src/version.ts"() {
|
|
680
|
-
questlogVersion = "0.
|
|
791
|
+
questlogVersion = "0.10.1";
|
|
681
792
|
}
|
|
682
793
|
});
|
|
683
794
|
|
|
@@ -979,8 +1090,62 @@ var init_tool_errors = __esm({
|
|
|
979
1090
|
}
|
|
980
1091
|
});
|
|
981
1092
|
|
|
1093
|
+
// src/tools/action_requirements.ts
|
|
1094
|
+
var actionRequirements;
|
|
1095
|
+
var init_action_requirements = __esm({
|
|
1096
|
+
"src/tools/action_requirements.ts"() {
|
|
1097
|
+
actionRequirements = {
|
|
1098
|
+
manage_hooks: { remove: ["id"] },
|
|
1099
|
+
shape_work: {
|
|
1100
|
+
create_quest: ["quest"],
|
|
1101
|
+
create_questline: ["questline"],
|
|
1102
|
+
attach_quest_to_questline: ["questId", "questlineId"],
|
|
1103
|
+
detach_quest_from_questline: ["questId"],
|
|
1104
|
+
dismiss_rumor: ["rumorId"],
|
|
1105
|
+
reopen_rumor: ["rumorId"],
|
|
1106
|
+
settle_rumor: ["rumorId"],
|
|
1107
|
+
update_rumor: ["rumorId"]
|
|
1108
|
+
},
|
|
1109
|
+
manage_repeatable: {
|
|
1110
|
+
create: ["repeatableQuest"],
|
|
1111
|
+
archive: ["repeatableQuestId"],
|
|
1112
|
+
update: ["repeatableQuestId"],
|
|
1113
|
+
spawn_due: ["now"]
|
|
1114
|
+
},
|
|
1115
|
+
organize_work: {
|
|
1116
|
+
add_unlock: ["fromQuestId", "toQuestId"],
|
|
1117
|
+
remove_unlock: ["fromQuestId", "toQuestId"],
|
|
1118
|
+
replace_unlocks: ["fromQuestIds", "toQuestId"],
|
|
1119
|
+
archive_questline: ["questlineId"],
|
|
1120
|
+
set_questline_fields: ["questlineId"]
|
|
1121
|
+
},
|
|
1122
|
+
plan_quest: { revise_objective: ["objective"] },
|
|
1123
|
+
run_quest: {
|
|
1124
|
+
abandon_and_spawn_followups: ["followups"],
|
|
1125
|
+
log_effort: ["effortSeconds"]
|
|
1126
|
+
},
|
|
1127
|
+
reward_work: { add: ["reward"], update: ["reward"], replace_repeatable_template: ["rewards"] }
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
1130
|
+
});
|
|
1131
|
+
|
|
982
1132
|
// src/tools/tool_metadata.ts
|
|
983
1133
|
function defineQuestlogTool(definition) {
|
|
1134
|
+
const requirements2 = actionRequirements[definition.name];
|
|
1135
|
+
if (requirements2) registerSchemaActions(definition.inputSchema, requirements2);
|
|
1136
|
+
const handler = definition.handler;
|
|
1137
|
+
definition.handler = (db, input) => {
|
|
1138
|
+
const errors = validateInput(definition.inputSchema, input);
|
|
1139
|
+
if (errors.length) {
|
|
1140
|
+
const action = input?.action;
|
|
1141
|
+
const example = minimalInput(definition.inputSchema, action);
|
|
1142
|
+
const message = errors.join("; ");
|
|
1143
|
+
return toolFailure("protocol", "invalid_input", message, message, {
|
|
1144
|
+
recovery: `${message}; pass ${JSON.stringify(example)}`
|
|
1145
|
+
});
|
|
1146
|
+
}
|
|
1147
|
+
return handler(db, input);
|
|
1148
|
+
};
|
|
984
1149
|
return definition;
|
|
985
1150
|
}
|
|
986
1151
|
function stringSchema(description, options = {}) {
|
|
@@ -1023,6 +1188,11 @@ function objectSchema(properties, required, description) {
|
|
|
1023
1188
|
}
|
|
1024
1189
|
var init_tool_metadata = __esm({
|
|
1025
1190
|
"src/tools/tool_metadata.ts"() {
|
|
1191
|
+
init_schema_actions();
|
|
1192
|
+
init_schema_examples();
|
|
1193
|
+
init_validate_input();
|
|
1194
|
+
init_action_requirements();
|
|
1195
|
+
init_tool_types();
|
|
1026
1196
|
}
|
|
1027
1197
|
});
|
|
1028
1198
|
|
|
@@ -1259,6 +1429,31 @@ var init_capture_rumor_tool = __esm({
|
|
|
1259
1429
|
}
|
|
1260
1430
|
});
|
|
1261
1431
|
|
|
1432
|
+
// src/hooks/list_deliveries.ts
|
|
1433
|
+
function listDeliveries(db, options = {}) {
|
|
1434
|
+
return db.prepare(
|
|
1435
|
+
`SELECT * FROM hook_deliveries ${options.includeDelivered ? "" : "WHERE status <> 'delivered'"} ORDER BY target_id, next_attempt_at, id`
|
|
1436
|
+
).all().map(
|
|
1437
|
+
(row) => ({
|
|
1438
|
+
id: String(row.id),
|
|
1439
|
+
targetId: Number(row.target_id),
|
|
1440
|
+
events: JSON.parse(String(row.events)),
|
|
1441
|
+
status: row.status,
|
|
1442
|
+
attempts: Number(row.attempts),
|
|
1443
|
+
lastAttemptAt: row.last_attempt_at == null ? null : Number(row.last_attempt_at),
|
|
1444
|
+
httpStatus: row.http_status == null ? null : Number(row.http_status),
|
|
1445
|
+
errorClass: row.error_class == null ? null : String(row.error_class),
|
|
1446
|
+
nextAttemptAt: Number(row.next_attempt_at)
|
|
1447
|
+
})
|
|
1448
|
+
).filter(
|
|
1449
|
+
(d) => options.questId === void 0 || d.events.some((e) => e.questId === options.questId)
|
|
1450
|
+
);
|
|
1451
|
+
}
|
|
1452
|
+
var init_list_deliveries = __esm({
|
|
1453
|
+
"src/hooks/list_deliveries.ts"() {
|
|
1454
|
+
}
|
|
1455
|
+
});
|
|
1456
|
+
|
|
1262
1457
|
// src/questlines/map_questline_row.ts
|
|
1263
1458
|
function mapQuestlineRow(row) {
|
|
1264
1459
|
return {
|
|
@@ -1805,7 +2000,8 @@ function inspectQuestlogItemToolHandler(db, input) {
|
|
|
1805
2000
|
detail: detailLevel === "full" ? detail : toQuestCompactDetail(detail)
|
|
1806
2001
|
},
|
|
1807
2002
|
related,
|
|
1808
|
-
detailLevel
|
|
2003
|
+
detailLevel,
|
|
2004
|
+
deliveries: listDeliveries(db, { questId: detail.id })
|
|
1809
2005
|
},
|
|
1810
2006
|
{ entities }
|
|
1811
2007
|
);
|
|
@@ -1875,6 +2071,7 @@ function inspectQuestlogItemToolHandler(db, input) {
|
|
|
1875
2071
|
var inspectQuestlogItemTool;
|
|
1876
2072
|
var init_inspect_questlog_item_tool = __esm({
|
|
1877
2073
|
"src/tools/inspect_questlog_item_tool.ts"() {
|
|
2074
|
+
init_list_deliveries();
|
|
1878
2075
|
init_get_questline_detail();
|
|
1879
2076
|
init_get_quest_detail();
|
|
1880
2077
|
init_get_repeatable_quest_or_throw();
|
|
@@ -1925,16 +2122,46 @@ var init_inspect_questlog_item_tool = __esm({
|
|
|
1925
2122
|
});
|
|
1926
2123
|
}
|
|
1927
2124
|
});
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
2125
|
+
function hookLabel(db, name) {
|
|
2126
|
+
const saved = db.prepare("SELECT label FROM hook_settings WHERE id = 1").get();
|
|
2127
|
+
if (saved) return String(saved.label);
|
|
2128
|
+
const path = String(
|
|
2129
|
+
db.prepare("PRAGMA database_list").all().find((row) => row.name === "main")?.file ?? ""
|
|
2130
|
+
);
|
|
2131
|
+
return path ? basename(path, extname(path)) : "questlog";
|
|
2132
|
+
}
|
|
2133
|
+
var init_hook_label = __esm({
|
|
2134
|
+
"src/hooks/hook_label.ts"() {
|
|
1933
2135
|
}
|
|
1934
|
-
|
|
2136
|
+
});
|
|
2137
|
+
|
|
2138
|
+
// src/hooks/list_hooks.ts
|
|
2139
|
+
function listHooks(db) {
|
|
2140
|
+
return db.prepare(`SELECT id, url, tag, events, min_gap, created_at, last_wake,
|
|
2141
|
+
(SELECT COUNT(*) FROM hook_assignments a LEFT JOIN hook_deliveries d ON d.id = a.delivery_id WHERE a.target_id = t.id AND (a.delivery_id IS NULL OR d.status = 'pending')) AS pending,
|
|
2142
|
+
(SELECT COUNT(*) FROM hook_deliveries d WHERE d.target_id = t.id AND d.status = 'failed') AS failed
|
|
2143
|
+
FROM hook_targets t ORDER BY id`).all().map((row) => {
|
|
2144
|
+
const url = new URL(String(row.url));
|
|
2145
|
+
url.username = "";
|
|
2146
|
+
url.password = "";
|
|
2147
|
+
url.search = "";
|
|
2148
|
+
url.hash = "";
|
|
2149
|
+
return {
|
|
2150
|
+
id: Number(row.id),
|
|
2151
|
+
scope: row.tag == null ? "database" : "tag",
|
|
2152
|
+
url: url.href,
|
|
2153
|
+
tag: row.tag == null ? null : String(row.tag),
|
|
2154
|
+
events: JSON.parse(String(row.events)),
|
|
2155
|
+
minGap: Number(row.min_gap),
|
|
2156
|
+
createdAt: Number(row.created_at),
|
|
2157
|
+
lastWake: row.last_wake == null ? null : Number(row.last_wake),
|
|
2158
|
+
pending: Number(row.pending),
|
|
2159
|
+
failed: Number(row.failed)
|
|
2160
|
+
};
|
|
2161
|
+
});
|
|
1935
2162
|
}
|
|
1936
|
-
var
|
|
1937
|
-
"src/
|
|
2163
|
+
var init_list_hooks = __esm({
|
|
2164
|
+
"src/hooks/list_hooks.ts"() {
|
|
1938
2165
|
}
|
|
1939
2166
|
});
|
|
1940
2167
|
|
|
@@ -1978,6 +2205,167 @@ var init_with_transaction = __esm({
|
|
|
1978
2205
|
}
|
|
1979
2206
|
});
|
|
1980
2207
|
|
|
2208
|
+
// src/hooks/remove_hook.ts
|
|
2209
|
+
function removeHook(db, id) {
|
|
2210
|
+
withTransaction(db, () => {
|
|
2211
|
+
db.prepare("DELETE FROM hook_assignments WHERE target_id = ?").run(id);
|
|
2212
|
+
db.prepare("DELETE FROM hook_deliveries WHERE target_id = ?").run(id);
|
|
2213
|
+
db.prepare("DELETE FROM hook_targets WHERE id = ?").run(id);
|
|
2214
|
+
});
|
|
2215
|
+
}
|
|
2216
|
+
var init_remove_hook = __esm({
|
|
2217
|
+
"src/hooks/remove_hook.ts"() {
|
|
2218
|
+
init_with_transaction();
|
|
2219
|
+
}
|
|
2220
|
+
});
|
|
2221
|
+
async function sendHookDelivery(db, delivery, target, options) {
|
|
2222
|
+
const now = options.now ?? options.clock?.() ?? Date.now();
|
|
2223
|
+
const timestamp = String(Math.floor(now / 1e3));
|
|
2224
|
+
const body = JSON.stringify({
|
|
2225
|
+
type: "questlog.events",
|
|
2226
|
+
db: hookLabel(db),
|
|
2227
|
+
deliveryId: delivery.id,
|
|
2228
|
+
sentAt: new Date(now).toISOString(),
|
|
2229
|
+
events: delivery.events
|
|
2230
|
+
});
|
|
2231
|
+
const signature = createHmac("sha256", target.secret).update(`${timestamp}.${body}`).digest("hex");
|
|
2232
|
+
let status = null;
|
|
2233
|
+
let errorClass = null;
|
|
2234
|
+
try {
|
|
2235
|
+
const response = await (options.fetch ?? globalThis.fetch)(target.url, {
|
|
2236
|
+
method: "POST",
|
|
2237
|
+
redirect: "error",
|
|
2238
|
+
headers: {
|
|
2239
|
+
"content-type": "application/json",
|
|
2240
|
+
"user-agent": `questlog/${questlogVersion}`,
|
|
2241
|
+
"x-questlog-delivery": delivery.id,
|
|
2242
|
+
"x-questlog-timestamp": timestamp,
|
|
2243
|
+
"x-questlog-signature": `sha256=${signature}`
|
|
2244
|
+
},
|
|
2245
|
+
body,
|
|
2246
|
+
signal: AbortSignal.timeout(Math.max(1, Math.min(options.timeoutMs ?? 5e3, 5e3)))
|
|
2247
|
+
});
|
|
2248
|
+
status = response.status;
|
|
2249
|
+
await response.body?.cancel();
|
|
2250
|
+
} catch (error) {
|
|
2251
|
+
errorClass = error instanceof Error && ["TimeoutError", "AbortError"].includes(error.name) ? error.name : "NetworkError";
|
|
2252
|
+
}
|
|
2253
|
+
const delivered = status != null && status >= 200 && status < 300;
|
|
2254
|
+
const completedAt = options.now ?? options.clock?.() ?? Date.now();
|
|
2255
|
+
withTransaction(db, () => {
|
|
2256
|
+
if (!db.prepare("SELECT id FROM hook_targets WHERE id = ? AND claim = ?").get(target.id, target.claim))
|
|
2257
|
+
return;
|
|
2258
|
+
const delay = Math.min(36e5, 3e4 * 2 ** (delivery.attempts - 1));
|
|
2259
|
+
const jitter = Number.parseInt(randomBytes(2).toString("hex"), 16) % 1e3;
|
|
2260
|
+
db.prepare(
|
|
2261
|
+
"UPDATE hook_deliveries SET status = ?, http_status = ?, error_class = ?, next_attempt_at = ? WHERE id = ?"
|
|
2262
|
+
).run(
|
|
2263
|
+
delivered ? "delivered" : delivery.attempts >= 8 ? "failed" : "pending",
|
|
2264
|
+
status,
|
|
2265
|
+
errorClass,
|
|
2266
|
+
completedAt + delay + jitter,
|
|
2267
|
+
delivery.id
|
|
2268
|
+
);
|
|
2269
|
+
db.prepare(
|
|
2270
|
+
"UPDATE hook_targets SET claim = NULL, lease_until = NULL WHERE id = ? AND claim = ?"
|
|
2271
|
+
).run(target.id, target.claim);
|
|
2272
|
+
db.prepare(
|
|
2273
|
+
"DELETE FROM hook_deliveries WHERE target_id = ? AND status = 'delivered' AND id NOT IN (SELECT id FROM hook_deliveries WHERE target_id = ? AND status = 'delivered' ORDER BY last_attempt_at DESC, id DESC LIMIT 500)"
|
|
2274
|
+
).run(target.id, target.id);
|
|
2275
|
+
});
|
|
2276
|
+
return delivered;
|
|
2277
|
+
}
|
|
2278
|
+
var init_send_hook_delivery = __esm({
|
|
2279
|
+
"src/hooks/send_hook_delivery.ts"() {
|
|
2280
|
+
init_version();
|
|
2281
|
+
init_with_transaction();
|
|
2282
|
+
init_hook_label();
|
|
2283
|
+
}
|
|
2284
|
+
});
|
|
2285
|
+
|
|
2286
|
+
// src/quests/get_quest_or_throw.ts
|
|
2287
|
+
function getQuestOrThrow(db, questId) {
|
|
2288
|
+
const row = db.prepare("SELECT * FROM quests WHERE id = ? AND deleted_at IS NULL").get(questId);
|
|
2289
|
+
if (!row) {
|
|
2290
|
+
throw new Error(`Quest ${questId} was not found.`);
|
|
2291
|
+
}
|
|
2292
|
+
return mapQuestRow(row);
|
|
2293
|
+
}
|
|
2294
|
+
var init_get_quest_or_throw = __esm({
|
|
2295
|
+
"src/quests/get_quest_or_throw.ts"() {
|
|
2296
|
+
init_map_quest_row();
|
|
2297
|
+
}
|
|
2298
|
+
});
|
|
2299
|
+
|
|
2300
|
+
// src/rewards/copy_repeatable_quest_rewards_to_quest.ts
|
|
2301
|
+
function copyRepeatableQuestRewardsToQuest(db, repeatableQuestId, questId, now) {
|
|
2302
|
+
const effectiveNow = resolveNow(now);
|
|
2303
|
+
const rows = db.prepare(
|
|
2304
|
+
`SELECT kind, name, description, quantity
|
|
2305
|
+
FROM repeatable_quest_rewards
|
|
2306
|
+
WHERE repeatable_quest_id = ? AND deleted_at IS NULL`
|
|
2307
|
+
).all(repeatableQuestId);
|
|
2308
|
+
for (const row of rows) {
|
|
2309
|
+
db.prepare(
|
|
2310
|
+
`INSERT INTO quest_rewards (quest_id, kind, name, description, quantity, created_at)
|
|
2311
|
+
VALUES (?, ?, ?, ?, ?, ?)`
|
|
2312
|
+
).run(
|
|
2313
|
+
questId,
|
|
2314
|
+
String(row.kind),
|
|
2315
|
+
String(row.name),
|
|
2316
|
+
row.description == null ? null : String(row.description),
|
|
2317
|
+
row.quantity == null ? null : Number(row.quantity),
|
|
2318
|
+
effectiveNow
|
|
2319
|
+
);
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
var init_copy_repeatable_quest_rewards_to_quest = __esm({
|
|
2323
|
+
"src/rewards/copy_repeatable_quest_rewards_to_quest.ts"() {
|
|
2324
|
+
init_resolve_now();
|
|
2325
|
+
}
|
|
2326
|
+
});
|
|
2327
|
+
|
|
2328
|
+
// src/tags/copy_repeatable_quest_tags_to_quest.ts
|
|
2329
|
+
function copyRepeatableQuestTagsToQuest(db, repeatableQuestId, questId, now) {
|
|
2330
|
+
const effectiveNow = resolveNow(now);
|
|
2331
|
+
const rows = db.prepare(
|
|
2332
|
+
`SELECT tag_id
|
|
2333
|
+
FROM repeatable_quest_tags
|
|
2334
|
+
WHERE repeatable_quest_id = ? AND deleted_at IS NULL`
|
|
2335
|
+
).all(repeatableQuestId);
|
|
2336
|
+
for (const row of rows) {
|
|
2337
|
+
db.prepare(
|
|
2338
|
+
`INSERT INTO quest_tags (quest_id, tag_id, created_at)
|
|
2339
|
+
VALUES (?, ?, ?)`
|
|
2340
|
+
).run(questId, Number(row.tag_id), effectiveNow);
|
|
2341
|
+
}
|
|
2342
|
+
}
|
|
2343
|
+
var init_copy_repeatable_quest_tags_to_quest = __esm({
|
|
2344
|
+
"src/tags/copy_repeatable_quest_tags_to_quest.ts"() {
|
|
2345
|
+
init_resolve_now();
|
|
2346
|
+
}
|
|
2347
|
+
});
|
|
2348
|
+
|
|
2349
|
+
// src/repeatable_quests/advance_anchor.ts
|
|
2350
|
+
function advanceAnchor(anchorAt, freq, interval) {
|
|
2351
|
+
const date = new Date(anchorAt);
|
|
2352
|
+
if (freq === "MONTHLY") {
|
|
2353
|
+
date.setUTCMonth(date.getUTCMonth() + interval);
|
|
2354
|
+
return date.getTime();
|
|
2355
|
+
}
|
|
2356
|
+
if (freq === "YEARLY") {
|
|
2357
|
+
date.setUTCFullYear(date.getUTCFullYear() + interval);
|
|
2358
|
+
return date.getTime();
|
|
2359
|
+
}
|
|
2360
|
+
return anchorAt + interval * WEEK_MS;
|
|
2361
|
+
}
|
|
2362
|
+
var WEEK_MS;
|
|
2363
|
+
var init_advance_anchor = __esm({
|
|
2364
|
+
"src/repeatable_quests/advance_anchor.ts"() {
|
|
2365
|
+
WEEK_MS = 7 * 864e5;
|
|
2366
|
+
}
|
|
2367
|
+
});
|
|
2368
|
+
|
|
1981
2369
|
// src/repeatable_quests/parse_rrule.ts
|
|
1982
2370
|
function parseRRule(rrule) {
|
|
1983
2371
|
const parts = Object.fromEntries(
|
|
@@ -2026,279 +2414,35 @@ var init_parse_rrule = __esm({
|
|
|
2026
2414
|
}
|
|
2027
2415
|
});
|
|
2028
2416
|
|
|
2029
|
-
// src/repeatable_quests/
|
|
2030
|
-
function
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
const now = resolveNow(input.now);
|
|
2034
|
-
const rrule = input.rrule ?? current.rrule;
|
|
2035
|
-
parseRRule(rrule);
|
|
2036
|
-
db.prepare(
|
|
2037
|
-
`UPDATE repeatable_quests
|
|
2038
|
-
SET questline_id = ?,
|
|
2039
|
-
title = ?,
|
|
2040
|
-
objective = ?,
|
|
2041
|
-
rrule = ?,
|
|
2042
|
-
anchor_at = ?,
|
|
2043
|
-
not_before_offset_seconds = ?,
|
|
2044
|
-
due_offset_seconds = ?,
|
|
2045
|
-
scheduled_start_offset_seconds = ?,
|
|
2046
|
-
scheduled_end_offset_seconds = ?,
|
|
2047
|
-
all_day = ?,
|
|
2048
|
-
estimate_seconds = ?,
|
|
2049
|
-
archived_at = ?,
|
|
2050
|
-
updated_at = ?
|
|
2051
|
-
WHERE id = ? AND deleted_at IS NULL`
|
|
2052
|
-
).run(
|
|
2053
|
-
input.questlineId === void 0 ? current.questlineId : input.questlineId,
|
|
2054
|
-
input.title == null ? current.title : assertNonEmpty(input.title, "Repeatable quest title"),
|
|
2055
|
-
input.objective == null ? current.objective : assertNonEmpty(input.objective, "Repeatable quest objective"),
|
|
2056
|
-
rrule,
|
|
2057
|
-
input.anchorAt ?? current.anchorAt,
|
|
2058
|
-
input.notBeforeOffsetSeconds === void 0 ? current.notBeforeOffsetSeconds : assertNonNegativeSeconds(input.notBeforeOffsetSeconds, "Repeatable not-before offset") ?? null,
|
|
2059
|
-
input.dueOffsetSeconds === void 0 ? current.dueOffsetSeconds : assertNonNegativeSeconds(input.dueOffsetSeconds, "Repeatable due offset") ?? null,
|
|
2060
|
-
input.scheduledStartOffsetSeconds === void 0 ? current.scheduledStartOffsetSeconds : input.scheduledStartOffsetSeconds,
|
|
2061
|
-
input.scheduledEndOffsetSeconds === void 0 ? current.scheduledEndOffsetSeconds : input.scheduledEndOffsetSeconds,
|
|
2062
|
-
input.allDay === void 0 ? current.allDay ? 1 : 0 : input.allDay ? 1 : 0,
|
|
2063
|
-
input.estimateSeconds === void 0 ? current.estimateSeconds : assertNonNegativeSeconds(input.estimateSeconds, "Repeatable estimate") ?? null,
|
|
2064
|
-
input.archivedAt === void 0 ? current.archivedAt : input.archivedAt,
|
|
2065
|
-
now,
|
|
2066
|
-
repeatableQuestId
|
|
2067
|
-
);
|
|
2068
|
-
return getRepeatableQuestOrThrow(db, repeatableQuestId);
|
|
2069
|
-
});
|
|
2417
|
+
// src/repeatable_quests/start_of_utc_day.ts
|
|
2418
|
+
function startOfUtcDay(timestamp) {
|
|
2419
|
+
const date = new Date(timestamp);
|
|
2420
|
+
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
|
|
2070
2421
|
}
|
|
2071
|
-
var
|
|
2072
|
-
"src/repeatable_quests/
|
|
2073
|
-
init_assert_non_empty();
|
|
2074
|
-
init_assert_non_negative_seconds();
|
|
2075
|
-
init_resolve_now();
|
|
2076
|
-
init_with_transaction();
|
|
2077
|
-
init_get_repeatable_quest_or_throw();
|
|
2078
|
-
init_parse_rrule();
|
|
2422
|
+
var init_start_of_utc_day = __esm({
|
|
2423
|
+
"src/repeatable_quests/start_of_utc_day.ts"() {
|
|
2079
2424
|
}
|
|
2080
2425
|
});
|
|
2081
2426
|
|
|
2082
|
-
// src/repeatable_quests/
|
|
2083
|
-
function
|
|
2084
|
-
|
|
2427
|
+
// src/repeatable_quests/start_of_utc_week.ts
|
|
2428
|
+
function startOfUtcWeek(dayStart) {
|
|
2429
|
+
const date = new Date(dayStart);
|
|
2430
|
+
return dayStart - date.getUTCDay() * DAY_MS;
|
|
2085
2431
|
}
|
|
2086
|
-
var
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2432
|
+
var DAY_MS;
|
|
2433
|
+
var init_start_of_utc_week = __esm({
|
|
2434
|
+
"src/repeatable_quests/start_of_utc_week.ts"() {
|
|
2435
|
+
DAY_MS = 864e5;
|
|
2090
2436
|
}
|
|
2091
2437
|
});
|
|
2092
2438
|
|
|
2093
|
-
// src/
|
|
2094
|
-
function
|
|
2095
|
-
const
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
var init_assert_active_row_exists = __esm({
|
|
2101
|
-
"src/assert_active_row_exists.ts"() {
|
|
2102
|
-
}
|
|
2103
|
-
});
|
|
2104
|
-
|
|
2105
|
-
// src/assert_non_negative_number.ts
|
|
2106
|
-
function assertNonNegativeNumber(value, label) {
|
|
2107
|
-
if (value != null && value < 0) {
|
|
2108
|
-
throw new Error(`${label} must not be negative.`);
|
|
2109
|
-
}
|
|
2110
|
-
return value;
|
|
2111
|
-
}
|
|
2112
|
-
var init_assert_non_negative_number = __esm({
|
|
2113
|
-
"src/assert_non_negative_number.ts"() {
|
|
2114
|
-
}
|
|
2115
|
-
});
|
|
2116
|
-
|
|
2117
|
-
// src/rewards/replace_active_repeatable_quest_rewards.ts
|
|
2118
|
-
function replaceActiveRepeatableQuestRewards(db, repeatableQuestId, rewards, now) {
|
|
2119
|
-
const effectiveNow = resolveNow(now);
|
|
2120
|
-
db.prepare(
|
|
2121
|
-
"UPDATE repeatable_quest_rewards SET deleted_at = ? WHERE repeatable_quest_id = ? AND deleted_at IS NULL"
|
|
2122
|
-
).run(effectiveNow, repeatableQuestId);
|
|
2123
|
-
for (const reward of rewards) {
|
|
2124
|
-
db.prepare(
|
|
2125
|
-
`INSERT INTO repeatable_quest_rewards (
|
|
2126
|
-
repeatable_quest_id,
|
|
2127
|
-
kind,
|
|
2128
|
-
name,
|
|
2129
|
-
description,
|
|
2130
|
-
quantity,
|
|
2131
|
-
created_at
|
|
2132
|
-
) VALUES (?, ?, ?, ?, ?, ?)`
|
|
2133
|
-
).run(
|
|
2134
|
-
repeatableQuestId,
|
|
2135
|
-
assertNonEmpty(reward.kind, "Reward kind"),
|
|
2136
|
-
assertNonEmpty(reward.name, "Reward name"),
|
|
2137
|
-
reward.description?.trim() || null,
|
|
2138
|
-
assertNonNegativeNumber(reward.quantity, "Reward quantity") ?? null,
|
|
2139
|
-
effectiveNow
|
|
2140
|
-
);
|
|
2141
|
-
}
|
|
2142
|
-
}
|
|
2143
|
-
var init_replace_active_repeatable_quest_rewards = __esm({
|
|
2144
|
-
"src/rewards/replace_active_repeatable_quest_rewards.ts"() {
|
|
2145
|
-
init_assert_non_empty();
|
|
2146
|
-
init_assert_non_negative_number();
|
|
2147
|
-
init_resolve_now();
|
|
2148
|
-
}
|
|
2149
|
-
});
|
|
2150
|
-
|
|
2151
|
-
// src/tags/replace_active_repeatable_quest_tags.ts
|
|
2152
|
-
function getOrCreateTagId(db, name, now) {
|
|
2153
|
-
const normalizedName = normalizeTagName(name);
|
|
2154
|
-
if (!normalizedName) {
|
|
2155
|
-
throw new Error("Tag name must not be empty.");
|
|
2156
|
-
}
|
|
2157
|
-
const existing = db.prepare("SELECT id FROM tags WHERE normalized_name = ? AND deleted_at IS NULL").get(normalizedName);
|
|
2158
|
-
if (existing) {
|
|
2159
|
-
return Number(existing.id);
|
|
2160
|
-
}
|
|
2161
|
-
const created = db.prepare(
|
|
2162
|
-
`INSERT INTO tags (name, normalized_name, created_at)
|
|
2163
|
-
VALUES (?, ?, ?)`
|
|
2164
|
-
).run(name.trim(), normalizedName, now);
|
|
2165
|
-
return Number(created.lastInsertRowid);
|
|
2166
|
-
}
|
|
2167
|
-
function replaceActiveRepeatableQuestTags(db, repeatableQuestId, tagNames, now) {
|
|
2168
|
-
const effectiveNow = resolveNow(now);
|
|
2169
|
-
db.prepare(
|
|
2170
|
-
"UPDATE repeatable_quest_tags SET deleted_at = ? WHERE repeatable_quest_id = ? AND deleted_at IS NULL"
|
|
2171
|
-
).run(effectiveNow, repeatableQuestId);
|
|
2172
|
-
const uniqueNames = [...new Set(tagNames.map((tag) => tag.trim()).filter(Boolean))];
|
|
2173
|
-
for (const name of uniqueNames) {
|
|
2174
|
-
const tagId = getOrCreateTagId(db, name, effectiveNow);
|
|
2175
|
-
db.prepare(
|
|
2176
|
-
`INSERT INTO repeatable_quest_tags (repeatable_quest_id, tag_id, created_at)
|
|
2177
|
-
VALUES (?, ?, ?)`
|
|
2178
|
-
).run(repeatableQuestId, tagId, effectiveNow);
|
|
2179
|
-
}
|
|
2180
|
-
}
|
|
2181
|
-
var init_replace_active_repeatable_quest_tags = __esm({
|
|
2182
|
-
"src/tags/replace_active_repeatable_quest_tags.ts"() {
|
|
2183
|
-
init_resolve_now();
|
|
2184
|
-
init_normalize_tag_name();
|
|
2185
|
-
}
|
|
2186
|
-
});
|
|
2187
|
-
|
|
2188
|
-
// src/repeatable_quests/create_repeatable_quest.ts
|
|
2189
|
-
function createRepeatableQuest(db, input) {
|
|
2190
|
-
return withTransaction(db, () => {
|
|
2191
|
-
const now = resolveNow(input.now);
|
|
2192
|
-
const title = assertNonEmpty(input.title, "Repeatable quest title");
|
|
2193
|
-
const objective = assertNonEmpty(input.objective, "Repeatable quest objective");
|
|
2194
|
-
parseRRule(input.rrule);
|
|
2195
|
-
if (input.questlineId != null) {
|
|
2196
|
-
assertActiveRowExists(db, "questlines", input.questlineId, "Questline");
|
|
2197
|
-
}
|
|
2198
|
-
const created = db.prepare(
|
|
2199
|
-
`INSERT INTO repeatable_quests (
|
|
2200
|
-
questline_id,
|
|
2201
|
-
title,
|
|
2202
|
-
objective,
|
|
2203
|
-
rrule,
|
|
2204
|
-
anchor_at,
|
|
2205
|
-
not_before_offset_seconds,
|
|
2206
|
-
due_offset_seconds,
|
|
2207
|
-
scheduled_start_offset_seconds,
|
|
2208
|
-
scheduled_end_offset_seconds,
|
|
2209
|
-
all_day,
|
|
2210
|
-
estimate_seconds,
|
|
2211
|
-
created_at,
|
|
2212
|
-
updated_at
|
|
2213
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
2214
|
-
).run(
|
|
2215
|
-
input.questlineId ?? null,
|
|
2216
|
-
title,
|
|
2217
|
-
objective,
|
|
2218
|
-
input.rrule,
|
|
2219
|
-
input.anchorAt,
|
|
2220
|
-
assertNonNegativeSeconds(input.notBeforeOffsetSeconds, "Repeatable not-before offset") ?? null,
|
|
2221
|
-
assertNonNegativeSeconds(input.dueOffsetSeconds, "Repeatable due offset") ?? null,
|
|
2222
|
-
input.scheduledStartOffsetSeconds ?? null,
|
|
2223
|
-
input.scheduledEndOffsetSeconds ?? null,
|
|
2224
|
-
input.allDay ? 1 : 0,
|
|
2225
|
-
assertNonNegativeSeconds(input.estimateSeconds, "Repeatable estimate") ?? null,
|
|
2226
|
-
now,
|
|
2227
|
-
now
|
|
2228
|
-
);
|
|
2229
|
-
const repeatableQuestId = Number(created.lastInsertRowid);
|
|
2230
|
-
if (input.tags && input.tags.length > 0) {
|
|
2231
|
-
replaceActiveRepeatableQuestTags(db, repeatableQuestId, input.tags, now);
|
|
2232
|
-
}
|
|
2233
|
-
if (input.rewards && input.rewards.length > 0) {
|
|
2234
|
-
replaceActiveRepeatableQuestRewards(db, repeatableQuestId, input.rewards, now);
|
|
2235
|
-
}
|
|
2236
|
-
return getRepeatableQuestOrThrow(db, repeatableQuestId);
|
|
2237
|
-
});
|
|
2238
|
-
}
|
|
2239
|
-
var init_create_repeatable_quest = __esm({
|
|
2240
|
-
"src/repeatable_quests/create_repeatable_quest.ts"() {
|
|
2241
|
-
init_assert_active_row_exists();
|
|
2242
|
-
init_assert_non_empty();
|
|
2243
|
-
init_assert_non_negative_seconds();
|
|
2244
|
-
init_resolve_now();
|
|
2245
|
-
init_replace_active_repeatable_quest_rewards();
|
|
2246
|
-
init_replace_active_repeatable_quest_tags();
|
|
2247
|
-
init_with_transaction();
|
|
2248
|
-
init_get_repeatable_quest_or_throw();
|
|
2249
|
-
init_parse_rrule();
|
|
2250
|
-
}
|
|
2251
|
-
});
|
|
2252
|
-
|
|
2253
|
-
// src/repeatable_quests/advance_anchor.ts
|
|
2254
|
-
function advanceAnchor(anchorAt, freq, interval) {
|
|
2255
|
-
const date = new Date(anchorAt);
|
|
2256
|
-
if (freq === "MONTHLY") {
|
|
2257
|
-
date.setUTCMonth(date.getUTCMonth() + interval);
|
|
2258
|
-
return date.getTime();
|
|
2259
|
-
}
|
|
2260
|
-
if (freq === "YEARLY") {
|
|
2261
|
-
date.setUTCFullYear(date.getUTCFullYear() + interval);
|
|
2262
|
-
return date.getTime();
|
|
2263
|
-
}
|
|
2264
|
-
return anchorAt + interval * WEEK_MS;
|
|
2265
|
-
}
|
|
2266
|
-
var WEEK_MS;
|
|
2267
|
-
var init_advance_anchor = __esm({
|
|
2268
|
-
"src/repeatable_quests/advance_anchor.ts"() {
|
|
2269
|
-
WEEK_MS = 7 * 864e5;
|
|
2270
|
-
}
|
|
2271
|
-
});
|
|
2272
|
-
|
|
2273
|
-
// src/repeatable_quests/start_of_utc_day.ts
|
|
2274
|
-
function startOfUtcDay(timestamp) {
|
|
2275
|
-
const date = new Date(timestamp);
|
|
2276
|
-
return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
|
|
2277
|
-
}
|
|
2278
|
-
var init_start_of_utc_day = __esm({
|
|
2279
|
-
"src/repeatable_quests/start_of_utc_day.ts"() {
|
|
2280
|
-
}
|
|
2281
|
-
});
|
|
2282
|
-
|
|
2283
|
-
// src/repeatable_quests/start_of_utc_week.ts
|
|
2284
|
-
function startOfUtcWeek(dayStart) {
|
|
2285
|
-
const date = new Date(dayStart);
|
|
2286
|
-
return dayStart - date.getUTCDay() * DAY_MS;
|
|
2287
|
-
}
|
|
2288
|
-
var DAY_MS;
|
|
2289
|
-
var init_start_of_utc_week = __esm({
|
|
2290
|
-
"src/repeatable_quests/start_of_utc_week.ts"() {
|
|
2291
|
-
DAY_MS = 864e5;
|
|
2292
|
-
}
|
|
2293
|
-
});
|
|
2294
|
-
|
|
2295
|
-
// src/repeatable_quests/generate_anchors_until.ts
|
|
2296
|
-
function generateAnchorsUntil(repeatable, now) {
|
|
2297
|
-
const rule = parseRRule(repeatable.rrule);
|
|
2298
|
-
const anchors = [];
|
|
2299
|
-
const until = rule.until == null ? now : Math.min(now, rule.until);
|
|
2300
|
-
if (repeatable.anchorAt > until) {
|
|
2301
|
-
return anchors;
|
|
2439
|
+
// src/repeatable_quests/generate_anchors_until.ts
|
|
2440
|
+
function generateAnchorsUntil(repeatable, now) {
|
|
2441
|
+
const rule = parseRRule(repeatable.rrule);
|
|
2442
|
+
const anchors = [];
|
|
2443
|
+
const until = rule.until == null ? now : Math.min(now, rule.until);
|
|
2444
|
+
if (repeatable.anchorAt > until) {
|
|
2445
|
+
return anchors;
|
|
2302
2446
|
}
|
|
2303
2447
|
if (rule.freq === "DAILY") {
|
|
2304
2448
|
let occurrence2 = 0;
|
|
@@ -2411,143 +2555,559 @@ var init_list_due_repeatable_quest_anchors = __esm({
|
|
|
2411
2555
|
}
|
|
2412
2556
|
});
|
|
2413
2557
|
|
|
2414
|
-
// src/repeatable_quests/
|
|
2415
|
-
function
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2558
|
+
// src/repeatable_quests/spawn_due_repeatable_quests.ts
|
|
2559
|
+
function spawnDueRepeatableQuests(db, now) {
|
|
2560
|
+
return withTransaction(db, () => {
|
|
2561
|
+
const anchors = listDueRepeatableQuestAnchors(db, now);
|
|
2562
|
+
const quests = [];
|
|
2563
|
+
for (const anchor of anchors) {
|
|
2564
|
+
const repeatable = getRepeatableQuestOrThrow(db, anchor.repeatableQuestId);
|
|
2565
|
+
const created = db.prepare(
|
|
2566
|
+
`INSERT INTO quests (
|
|
2567
|
+
questline_id,
|
|
2568
|
+
source_rumor_id,
|
|
2569
|
+
spawned_by_quest_id,
|
|
2570
|
+
spawned_from_repeatable_id,
|
|
2571
|
+
spawned_for_at,
|
|
2572
|
+
title,
|
|
2573
|
+
objective,
|
|
2574
|
+
created_at,
|
|
2575
|
+
updated_at,
|
|
2576
|
+
effort_seconds,
|
|
2577
|
+
estimate_seconds,
|
|
2578
|
+
not_before_at,
|
|
2579
|
+
due_at,
|
|
2580
|
+
scheduled_start_at,
|
|
2581
|
+
scheduled_end_at,
|
|
2582
|
+
all_day
|
|
2583
|
+
) VALUES (?, NULL, NULL, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?)`
|
|
2584
|
+
).run(
|
|
2585
|
+
repeatable.questlineId ?? null,
|
|
2586
|
+
repeatable.id,
|
|
2587
|
+
anchor.anchorAt,
|
|
2588
|
+
repeatable.title,
|
|
2589
|
+
repeatable.objective,
|
|
2590
|
+
now,
|
|
2591
|
+
now,
|
|
2592
|
+
repeatable.estimateSeconds ?? null,
|
|
2593
|
+
repeatable.notBeforeOffsetSeconds == null ? null : anchor.anchorAt + repeatable.notBeforeOffsetSeconds * 1e3,
|
|
2594
|
+
repeatable.dueOffsetSeconds == null ? null : anchor.anchorAt + repeatable.dueOffsetSeconds * 1e3,
|
|
2595
|
+
repeatable.scheduledStartOffsetSeconds == null ? null : anchor.anchorAt + repeatable.scheduledStartOffsetSeconds * 1e3,
|
|
2596
|
+
repeatable.scheduledEndOffsetSeconds == null ? null : anchor.anchorAt + repeatable.scheduledEndOffsetSeconds * 1e3,
|
|
2597
|
+
repeatable.allDay ? 1 : 0
|
|
2598
|
+
);
|
|
2599
|
+
const questId = Number(created.lastInsertRowid);
|
|
2600
|
+
copyRepeatableQuestTagsToQuest(db, repeatable.id, questId, now);
|
|
2601
|
+
copyRepeatableQuestRewardsToQuest(db, repeatable.id, questId, now);
|
|
2602
|
+
quests.push(getQuestOrThrow(db, questId));
|
|
2603
|
+
}
|
|
2604
|
+
return quests;
|
|
2605
|
+
});
|
|
2606
|
+
}
|
|
2607
|
+
var init_spawn_due_repeatable_quests = __esm({
|
|
2608
|
+
"src/repeatable_quests/spawn_due_repeatable_quests.ts"() {
|
|
2609
|
+
init_get_quest_or_throw();
|
|
2610
|
+
init_copy_repeatable_quest_rewards_to_quest();
|
|
2611
|
+
init_copy_repeatable_quest_tags_to_quest();
|
|
2612
|
+
init_with_transaction();
|
|
2613
|
+
init_get_repeatable_quest_or_throw();
|
|
2614
|
+
init_list_due_repeatable_quest_anchors();
|
|
2615
|
+
}
|
|
2616
|
+
});
|
|
2617
|
+
|
|
2618
|
+
// src/hooks/detect_hook_events.ts
|
|
2619
|
+
function detectHookEvents(db, now) {
|
|
2620
|
+
const events = [];
|
|
2621
|
+
const add = (type, at, ids) => {
|
|
2622
|
+
events.push({
|
|
2623
|
+
id: `e_${type}_${ids.questId ?? ids.rumorId}_${ids.repeatableId ?? ""}_${at}`,
|
|
2624
|
+
type,
|
|
2625
|
+
...ids,
|
|
2626
|
+
occurredAt: new Date(at).toISOString()
|
|
2627
|
+
});
|
|
2628
|
+
};
|
|
2629
|
+
for (const { detail: q } of queryQuestDetailRecords(db, {}, now)) {
|
|
2630
|
+
if (q.resolvedAt == null && q.effectiveDueAt != null && q.effectiveDueAt <= now) {
|
|
2631
|
+
add("quest.due", q.effectiveDueAt, { questId: q.id });
|
|
2632
|
+
if (q.effectiveDueAt < now) add("quest.overdue", q.effectiveDueAt, { questId: q.id });
|
|
2633
|
+
}
|
|
2634
|
+
if (q.available) {
|
|
2635
|
+
const prerequisites = db.prepare(
|
|
2636
|
+
`SELECT p.id, p.resolved_at, u.created_at FROM quest_unlocks u JOIN quests p ON p.id = u.from_quest_id WHERE u.to_quest_id = ? AND u.deleted_at IS NULL AND p.deleted_at IS NULL AND p.success = 1 AND p.resolved_at IS NOT NULL`
|
|
2637
|
+
).all(q.id);
|
|
2638
|
+
if (prerequisites.some((p) => Number(p.resolved_at) >= Number(p.created_at))) {
|
|
2639
|
+
const at = Math.max(q.notBeforeAt ?? 0, ...prerequisites.map((p) => Number(p.resolved_at)));
|
|
2640
|
+
if (at <= now) add("quest.unblocked", at, { questId: q.id });
|
|
2641
|
+
}
|
|
2642
|
+
}
|
|
2643
|
+
if (q.spawnedFromRepeatableId != null && q.createdAt <= now)
|
|
2644
|
+
add("repeatable.spawned", q.createdAt, {
|
|
2645
|
+
questId: q.id,
|
|
2646
|
+
repeatableId: q.spawnedFromRepeatableId
|
|
2647
|
+
});
|
|
2648
|
+
}
|
|
2649
|
+
for (const row of db.prepare("SELECT id, created_at FROM rumors WHERE deleted_at IS NULL AND created_at <= ?").all(now))
|
|
2650
|
+
add("rumor.captured", Number(row.created_at), { rumorId: Number(row.id) });
|
|
2651
|
+
return events.sort(
|
|
2652
|
+
(a, b) => a.occurredAt.localeCompare(b.occurredAt) || a.id.localeCompare(b.id)
|
|
2653
|
+
);
|
|
2654
|
+
}
|
|
2655
|
+
var init_detect_hook_events = __esm({
|
|
2656
|
+
"src/hooks/detect_hook_events.ts"() {
|
|
2657
|
+
init_query_quest_detail_records();
|
|
2658
|
+
}
|
|
2659
|
+
});
|
|
2660
|
+
async function tickQuestlog(db, options = {}) {
|
|
2661
|
+
const now = options.now ?? options.clock?.() ?? Date.now();
|
|
2662
|
+
const result = {
|
|
2663
|
+
events: [],
|
|
2664
|
+
batches: [],
|
|
2665
|
+
sent: 0,
|
|
2666
|
+
queued: 0,
|
|
2667
|
+
retrying: 0,
|
|
2668
|
+
failed: 0
|
|
2669
|
+
};
|
|
2670
|
+
const claims = [];
|
|
2671
|
+
const collect = () => {
|
|
2672
|
+
spawnDueRepeatableQuests(db, now);
|
|
2673
|
+
const detected = detectHookEvents(db, now);
|
|
2674
|
+
result.events = detected.filter(
|
|
2675
|
+
(e) => !db.prepare("SELECT id FROM hook_events WHERE id = ?").get(e.id)
|
|
2676
|
+
);
|
|
2677
|
+
for (const event of result.events)
|
|
2678
|
+
db.prepare("INSERT INTO hook_events(id, occurred_at, event) VALUES (?, ?, ?)").run(
|
|
2679
|
+
event.id,
|
|
2680
|
+
Date.parse(event.occurredAt),
|
|
2681
|
+
JSON.stringify(event)
|
|
2682
|
+
);
|
|
2683
|
+
for (const target of listHooks(db)) {
|
|
2684
|
+
const events = db.prepare(
|
|
2685
|
+
"SELECT e.event FROM hook_events e WHERE occurred_at >= ? AND NOT EXISTS (SELECT 1 FROM hook_assignments a WHERE a.target_id = ? AND a.event_id = e.id) ORDER BY occurred_at, id"
|
|
2686
|
+
).all(target.createdAt, target.id).map((row2) => JSON.parse(String(row2.event))).filter((event) => {
|
|
2687
|
+
if (!target.events.includes(event.type)) return false;
|
|
2688
|
+
if (target.tag == null) return true;
|
|
2689
|
+
return event.questId !== void 0 && db.prepare(
|
|
2690
|
+
"SELECT 1 FROM quest_tags qt JOIN tags t ON t.id = qt.tag_id WHERE qt.quest_id = ? AND qt.deleted_at IS NULL AND t.deleted_at IS NULL AND t.normalized_name = ?"
|
|
2691
|
+
).get(event.questId, normalizeTagName(target.tag)) !== void 0;
|
|
2692
|
+
});
|
|
2693
|
+
for (const event of events)
|
|
2694
|
+
db.prepare("INSERT INTO hook_assignments(target_id, event_id) VALUES (?, ?)").run(
|
|
2695
|
+
target.id,
|
|
2696
|
+
event.id
|
|
2697
|
+
);
|
|
2698
|
+
const row = db.prepare("SELECT url, secret, lease_until FROM hook_targets WHERE id = ?").get(target.id);
|
|
2699
|
+
if (!row || row.lease_until != null && Number(row.lease_until) > now) continue;
|
|
2700
|
+
let pending = listDeliveries(db).find(
|
|
2701
|
+
(d) => d.targetId === target.id && d.status === "pending"
|
|
2702
|
+
);
|
|
2703
|
+
const queued = db.prepare(
|
|
2704
|
+
"SELECT e.event FROM hook_assignments a JOIN hook_events e ON e.id = a.event_id WHERE a.target_id = ? AND a.delivery_id IS NULL ORDER BY e.occurred_at, e.id"
|
|
2705
|
+
).all(target.id).map((r) => JSON.parse(String(r.event)));
|
|
2706
|
+
if (!pending && queued.length) {
|
|
2707
|
+
const id = `d_${randomBytes(16).toString("hex")}`;
|
|
2708
|
+
db.prepare(
|
|
2709
|
+
"INSERT INTO hook_deliveries(id, target_id, events, status, next_attempt_at) VALUES (?, ?, ?, 'pending', ?)"
|
|
2710
|
+
).run(
|
|
2711
|
+
id,
|
|
2712
|
+
target.id,
|
|
2713
|
+
JSON.stringify(queued),
|
|
2714
|
+
Math.max(now, (target.lastWake ?? -Infinity) + target.minGap * 1e3)
|
|
2715
|
+
);
|
|
2716
|
+
db.prepare(
|
|
2717
|
+
"UPDATE hook_assignments SET delivery_id = ? WHERE target_id = ? AND delivery_id IS NULL"
|
|
2718
|
+
).run(id, target.id);
|
|
2719
|
+
pending = listDeliveries(db).find((d) => d.id === id);
|
|
2720
|
+
}
|
|
2721
|
+
if (!pending) continue;
|
|
2722
|
+
if (pending.attempts === 0 && queued.length && !queued.every((e) => pending?.events.some((p) => p.id === e.id))) {
|
|
2723
|
+
pending.events.push(...queued);
|
|
2724
|
+
db.prepare("UPDATE hook_deliveries SET events = ? WHERE id = ?").run(
|
|
2725
|
+
JSON.stringify(pending.events),
|
|
2726
|
+
pending.id
|
|
2727
|
+
);
|
|
2728
|
+
db.prepare(
|
|
2729
|
+
"UPDATE hook_assignments SET delivery_id = ? WHERE target_id = ? AND delivery_id IS NULL"
|
|
2730
|
+
).run(pending.id, target.id);
|
|
2731
|
+
}
|
|
2732
|
+
if (pending.nextAttemptAt > now || target.lastWake != null && target.lastWake + target.minGap * 1e3 > now)
|
|
2733
|
+
continue;
|
|
2734
|
+
if (pending.attempts >= 8) {
|
|
2735
|
+
db.prepare("UPDATE hook_deliveries SET status = 'failed' WHERE id = ?").run(pending.id);
|
|
2736
|
+
continue;
|
|
2737
|
+
}
|
|
2738
|
+
const claim = randomBytes(16).toString("hex");
|
|
2739
|
+
db.prepare(
|
|
2740
|
+
"UPDATE hook_targets SET claim = ?, lease_until = ?, last_wake = ? WHERE id = ?"
|
|
2741
|
+
).run(claim, now + 12e4, now, target.id);
|
|
2742
|
+
db.prepare(
|
|
2743
|
+
"UPDATE hook_deliveries SET attempts = attempts + 1, last_attempt_at = ? WHERE id = ?"
|
|
2744
|
+
).run(now, pending.id);
|
|
2745
|
+
pending.attempts++;
|
|
2746
|
+
pending.lastAttemptAt = now;
|
|
2747
|
+
result.batches.push({ targetId: target.id, events: pending.events });
|
|
2748
|
+
claims.push({
|
|
2749
|
+
delivery: pending,
|
|
2750
|
+
target: { id: target.id, url: String(row.url), secret: String(row.secret), claim }
|
|
2751
|
+
});
|
|
2752
|
+
}
|
|
2753
|
+
};
|
|
2754
|
+
if (options.dryRun) {
|
|
2755
|
+
const rollback = new Error("preview rollback");
|
|
2756
|
+
try {
|
|
2757
|
+
withTransaction(db, () => {
|
|
2758
|
+
collect();
|
|
2759
|
+
throw rollback;
|
|
2760
|
+
});
|
|
2761
|
+
} catch (error) {
|
|
2762
|
+
if (error !== rollback) throw error;
|
|
2763
|
+
}
|
|
2764
|
+
} else withTransaction(db, collect);
|
|
2765
|
+
if (!options.dryRun) {
|
|
2766
|
+
const sent = await Promise.all(
|
|
2767
|
+
claims.map((claim) => sendHookDelivery(db, claim.delivery, claim.target, options))
|
|
2768
|
+
);
|
|
2769
|
+
result.sent = sent.filter(Boolean).length;
|
|
2770
|
+
}
|
|
2771
|
+
const deliveries = listDeliveries(db);
|
|
2772
|
+
result.queued = deliveries.filter((d) => d.status === "pending" && d.attempts === 0).length + Number(
|
|
2773
|
+
db.prepare(
|
|
2774
|
+
"SELECT COUNT(DISTINCT target_id) AS n FROM hook_assignments WHERE delivery_id IS NULL"
|
|
2775
|
+
).get()?.n ?? 0
|
|
2776
|
+
);
|
|
2777
|
+
result.retrying = deliveries.filter((d) => d.status === "pending" && d.attempts > 0).length;
|
|
2778
|
+
result.failed = deliveries.filter((d) => d.status === "failed").length;
|
|
2779
|
+
return result;
|
|
2780
|
+
}
|
|
2781
|
+
var init_tick_questlog = __esm({
|
|
2782
|
+
"src/hooks/tick_questlog.ts"() {
|
|
2783
|
+
init_spawn_due_repeatable_quests();
|
|
2784
|
+
init_normalize_tag_name();
|
|
2785
|
+
init_with_transaction();
|
|
2786
|
+
init_detect_hook_events();
|
|
2787
|
+
init_list_deliveries();
|
|
2788
|
+
init_list_hooks();
|
|
2789
|
+
init_send_hook_delivery();
|
|
2790
|
+
}
|
|
2791
|
+
});
|
|
2792
|
+
|
|
2793
|
+
// src/hooks/index.ts
|
|
2794
|
+
var init_hooks = __esm({
|
|
2795
|
+
"src/hooks/index.ts"() {
|
|
2796
|
+
init_list_deliveries();
|
|
2797
|
+
init_list_hooks();
|
|
2798
|
+
init_remove_hook();
|
|
2799
|
+
init_tick_questlog();
|
|
2800
|
+
}
|
|
2801
|
+
});
|
|
2802
|
+
|
|
2803
|
+
// src/tools/manage_hooks_tool.ts
|
|
2804
|
+
var manageHooksTool;
|
|
2805
|
+
var init_manage_hooks_tool = __esm({
|
|
2806
|
+
"src/tools/manage_hooks_tool.ts"() {
|
|
2807
|
+
init_hooks();
|
|
2808
|
+
init_tool_metadata();
|
|
2809
|
+
init_tool_types();
|
|
2810
|
+
manageHooksTool = defineQuestlogTool({
|
|
2811
|
+
name: "manage_hooks",
|
|
2812
|
+
description: "List or remove outbound webhook targets, run a tick, or inspect pending and failed deliveries.",
|
|
2813
|
+
whenToUse: "Use to operate configured outbound webhooks. Add and test targets with the CLI.",
|
|
2814
|
+
whenNotToUse: "Use quest tools to change work itself.",
|
|
2815
|
+
sideEffects: "writes_state",
|
|
2816
|
+
readOnly: false,
|
|
2817
|
+
supportsClarification: false,
|
|
2818
|
+
targetKinds: ["hook"],
|
|
2819
|
+
inputDescriptions: {
|
|
2820
|
+
action: "Operation to perform.",
|
|
2821
|
+
id: "Target id for removal.",
|
|
2822
|
+
dry_run: "Preview a tick without recording or sending."
|
|
2823
|
+
},
|
|
2824
|
+
outputDescription: "Returns redacted targets, delivery metadata, or a tick summary. Never returns signing secrets.",
|
|
2825
|
+
inputSchema: objectSchema(
|
|
2826
|
+
{
|
|
2827
|
+
action: enumSchema("Operation.", ["list", "remove", "tick", "deliveries"]),
|
|
2828
|
+
id: integerSchema("Target id."),
|
|
2829
|
+
dry_run: booleanSchema("Preview only.")
|
|
2830
|
+
},
|
|
2831
|
+
["action"]
|
|
2832
|
+
),
|
|
2833
|
+
handler: async (db, input) => {
|
|
2834
|
+
switch (input.action) {
|
|
2835
|
+
case "list":
|
|
2836
|
+
return toolSuccess("Webhook targets.", listHooks(db));
|
|
2837
|
+
case "deliveries":
|
|
2838
|
+
return toolSuccess("Pending and failed deliveries.", listDeliveries(db));
|
|
2839
|
+
case "remove":
|
|
2840
|
+
if (!Number.isSafeInteger(input.id) || Number(input.id) < 1)
|
|
2841
|
+
return toolFailure(
|
|
2842
|
+
"protocol",
|
|
2843
|
+
"invalid_input",
|
|
2844
|
+
"Target id required.",
|
|
2845
|
+
"Provide a positive target id.",
|
|
2846
|
+
{ recovery: 'Pass {"action":"remove","id":1}' }
|
|
2847
|
+
);
|
|
2848
|
+
removeHook(db, Number(input.id));
|
|
2849
|
+
return toolSuccess("Webhook target removed.", {});
|
|
2850
|
+
case "tick":
|
|
2851
|
+
return toolSuccess(
|
|
2852
|
+
"Webhook tick completed.",
|
|
2853
|
+
await tickQuestlog(db, { dryRun: input.dry_run ?? false })
|
|
2854
|
+
);
|
|
2855
|
+
}
|
|
2856
|
+
}
|
|
2857
|
+
});
|
|
2858
|
+
}
|
|
2859
|
+
});
|
|
2860
|
+
|
|
2861
|
+
// src/assert_non_negative_seconds.ts
|
|
2862
|
+
function assertNonNegativeSeconds(value, label) {
|
|
2863
|
+
if (value != null && value < 0) {
|
|
2864
|
+
throw new Error(`${label} must not be negative.`);
|
|
2865
|
+
}
|
|
2866
|
+
return value;
|
|
2867
|
+
}
|
|
2868
|
+
var init_assert_non_negative_seconds = __esm({
|
|
2869
|
+
"src/assert_non_negative_seconds.ts"() {
|
|
2870
|
+
}
|
|
2871
|
+
});
|
|
2872
|
+
|
|
2873
|
+
// src/repeatable_quests/update_repeatable_quest.ts
|
|
2874
|
+
function updateRepeatableQuest(db, repeatableQuestId, input) {
|
|
2875
|
+
return withTransaction(db, () => {
|
|
2876
|
+
const current = getRepeatableQuestOrThrow(db, repeatableQuestId);
|
|
2877
|
+
const now = resolveNow(input.now);
|
|
2878
|
+
const rrule = input.rrule ?? current.rrule;
|
|
2879
|
+
parseRRule(rrule);
|
|
2880
|
+
db.prepare(
|
|
2881
|
+
`UPDATE repeatable_quests
|
|
2882
|
+
SET questline_id = ?,
|
|
2883
|
+
title = ?,
|
|
2884
|
+
objective = ?,
|
|
2885
|
+
rrule = ?,
|
|
2886
|
+
anchor_at = ?,
|
|
2887
|
+
not_before_offset_seconds = ?,
|
|
2888
|
+
due_offset_seconds = ?,
|
|
2889
|
+
scheduled_start_offset_seconds = ?,
|
|
2890
|
+
scheduled_end_offset_seconds = ?,
|
|
2891
|
+
all_day = ?,
|
|
2892
|
+
estimate_seconds = ?,
|
|
2893
|
+
archived_at = ?,
|
|
2894
|
+
updated_at = ?
|
|
2895
|
+
WHERE id = ? AND deleted_at IS NULL`
|
|
2896
|
+
).run(
|
|
2897
|
+
input.questlineId === void 0 ? current.questlineId : input.questlineId,
|
|
2898
|
+
input.title == null ? current.title : assertNonEmpty(input.title, "Repeatable quest title"),
|
|
2899
|
+
input.objective == null ? current.objective : assertNonEmpty(input.objective, "Repeatable quest objective"),
|
|
2900
|
+
rrule,
|
|
2901
|
+
input.anchorAt ?? current.anchorAt,
|
|
2902
|
+
input.notBeforeOffsetSeconds === void 0 ? current.notBeforeOffsetSeconds : assertNonNegativeSeconds(input.notBeforeOffsetSeconds, "Repeatable not-before offset") ?? null,
|
|
2903
|
+
input.dueOffsetSeconds === void 0 ? current.dueOffsetSeconds : assertNonNegativeSeconds(input.dueOffsetSeconds, "Repeatable due offset") ?? null,
|
|
2904
|
+
input.scheduledStartOffsetSeconds === void 0 ? current.scheduledStartOffsetSeconds : input.scheduledStartOffsetSeconds,
|
|
2905
|
+
input.scheduledEndOffsetSeconds === void 0 ? current.scheduledEndOffsetSeconds : input.scheduledEndOffsetSeconds,
|
|
2906
|
+
input.allDay === void 0 ? current.allDay ? 1 : 0 : input.allDay ? 1 : 0,
|
|
2907
|
+
input.estimateSeconds === void 0 ? current.estimateSeconds : assertNonNegativeSeconds(input.estimateSeconds, "Repeatable estimate") ?? null,
|
|
2908
|
+
input.archivedAt === void 0 ? current.archivedAt : input.archivedAt,
|
|
2909
|
+
now,
|
|
2910
|
+
repeatableQuestId
|
|
2911
|
+
);
|
|
2912
|
+
return getRepeatableQuestOrThrow(db, repeatableQuestId);
|
|
2913
|
+
});
|
|
2914
|
+
}
|
|
2915
|
+
var init_update_repeatable_quest = __esm({
|
|
2916
|
+
"src/repeatable_quests/update_repeatable_quest.ts"() {
|
|
2917
|
+
init_assert_non_empty();
|
|
2918
|
+
init_assert_non_negative_seconds();
|
|
2919
|
+
init_resolve_now();
|
|
2920
|
+
init_with_transaction();
|
|
2921
|
+
init_get_repeatable_quest_or_throw();
|
|
2922
|
+
init_parse_rrule();
|
|
2923
|
+
}
|
|
2924
|
+
});
|
|
2925
|
+
|
|
2926
|
+
// src/repeatable_quests/archive_repeatable_quest.ts
|
|
2927
|
+
function archiveRepeatableQuest(db, repeatableQuestId, archivedAt) {
|
|
2928
|
+
return updateRepeatableQuest(db, repeatableQuestId, { archivedAt: resolveNow(archivedAt) });
|
|
2423
2929
|
}
|
|
2424
|
-
var
|
|
2425
|
-
"src/repeatable_quests/
|
|
2930
|
+
var init_archive_repeatable_quest = __esm({
|
|
2931
|
+
"src/repeatable_quests/archive_repeatable_quest.ts"() {
|
|
2426
2932
|
init_resolve_now();
|
|
2427
|
-
|
|
2933
|
+
init_update_repeatable_quest();
|
|
2428
2934
|
}
|
|
2429
2935
|
});
|
|
2430
2936
|
|
|
2431
|
-
// src/
|
|
2432
|
-
function
|
|
2433
|
-
const row = db.prepare(
|
|
2937
|
+
// src/assert_active_row_exists.ts
|
|
2938
|
+
function assertActiveRowExists(db, table, id, label) {
|
|
2939
|
+
const row = db.prepare(`SELECT id FROM ${table} WHERE id = ? AND deleted_at IS NULL`).get(id);
|
|
2434
2940
|
if (!row) {
|
|
2435
|
-
throw new Error(
|
|
2941
|
+
throw new Error(`${label} ${id} was not found.`);
|
|
2436
2942
|
}
|
|
2437
|
-
return mapQuestRow(row);
|
|
2438
2943
|
}
|
|
2439
|
-
var
|
|
2440
|
-
"src/
|
|
2441
|
-
init_map_quest_row();
|
|
2944
|
+
var init_assert_active_row_exists = __esm({
|
|
2945
|
+
"src/assert_active_row_exists.ts"() {
|
|
2442
2946
|
}
|
|
2443
2947
|
});
|
|
2444
2948
|
|
|
2445
|
-
// src/
|
|
2446
|
-
function
|
|
2949
|
+
// src/assert_non_negative_number.ts
|
|
2950
|
+
function assertNonNegativeNumber(value, label) {
|
|
2951
|
+
if (value != null && value < 0) {
|
|
2952
|
+
throw new Error(`${label} must not be negative.`);
|
|
2953
|
+
}
|
|
2954
|
+
return value;
|
|
2955
|
+
}
|
|
2956
|
+
var init_assert_non_negative_number = __esm({
|
|
2957
|
+
"src/assert_non_negative_number.ts"() {
|
|
2958
|
+
}
|
|
2959
|
+
});
|
|
2960
|
+
|
|
2961
|
+
// src/rewards/replace_active_repeatable_quest_rewards.ts
|
|
2962
|
+
function replaceActiveRepeatableQuestRewards(db, repeatableQuestId, rewards, now) {
|
|
2447
2963
|
const effectiveNow = resolveNow(now);
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
).all(repeatableQuestId);
|
|
2453
|
-
for (const row of rows) {
|
|
2964
|
+
db.prepare(
|
|
2965
|
+
"UPDATE repeatable_quest_rewards SET deleted_at = ? WHERE repeatable_quest_id = ? AND deleted_at IS NULL"
|
|
2966
|
+
).run(effectiveNow, repeatableQuestId);
|
|
2967
|
+
for (const reward of rewards) {
|
|
2454
2968
|
db.prepare(
|
|
2455
|
-
`INSERT INTO
|
|
2456
|
-
|
|
2969
|
+
`INSERT INTO repeatable_quest_rewards (
|
|
2970
|
+
repeatable_quest_id,
|
|
2971
|
+
kind,
|
|
2972
|
+
name,
|
|
2973
|
+
description,
|
|
2974
|
+
quantity,
|
|
2975
|
+
created_at
|
|
2976
|
+
) VALUES (?, ?, ?, ?, ?, ?)`
|
|
2457
2977
|
).run(
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2978
|
+
repeatableQuestId,
|
|
2979
|
+
assertNonEmpty(reward.kind, "Reward kind"),
|
|
2980
|
+
assertNonEmpty(reward.name, "Reward name"),
|
|
2981
|
+
reward.description?.trim() || null,
|
|
2982
|
+
assertNonNegativeNumber(reward.quantity, "Reward quantity") ?? null,
|
|
2463
2983
|
effectiveNow
|
|
2464
2984
|
);
|
|
2465
2985
|
}
|
|
2466
2986
|
}
|
|
2467
|
-
var
|
|
2468
|
-
"src/rewards/
|
|
2987
|
+
var init_replace_active_repeatable_quest_rewards = __esm({
|
|
2988
|
+
"src/rewards/replace_active_repeatable_quest_rewards.ts"() {
|
|
2989
|
+
init_assert_non_empty();
|
|
2990
|
+
init_assert_non_negative_number();
|
|
2469
2991
|
init_resolve_now();
|
|
2470
2992
|
}
|
|
2471
2993
|
});
|
|
2472
2994
|
|
|
2473
|
-
// src/tags/
|
|
2474
|
-
function
|
|
2995
|
+
// src/tags/replace_active_repeatable_quest_tags.ts
|
|
2996
|
+
function getOrCreateTagId(db, name, now) {
|
|
2997
|
+
const normalizedName = normalizeTagName(name);
|
|
2998
|
+
if (!normalizedName) {
|
|
2999
|
+
throw new Error("Tag name must not be empty.");
|
|
3000
|
+
}
|
|
3001
|
+
const existing = db.prepare("SELECT id FROM tags WHERE normalized_name = ? AND deleted_at IS NULL").get(normalizedName);
|
|
3002
|
+
if (existing) {
|
|
3003
|
+
return Number(existing.id);
|
|
3004
|
+
}
|
|
3005
|
+
const created = db.prepare(
|
|
3006
|
+
`INSERT INTO tags (name, normalized_name, created_at)
|
|
3007
|
+
VALUES (?, ?, ?)`
|
|
3008
|
+
).run(name.trim(), normalizedName, now);
|
|
3009
|
+
return Number(created.lastInsertRowid);
|
|
3010
|
+
}
|
|
3011
|
+
function replaceActiveRepeatableQuestTags(db, repeatableQuestId, tagNames, now) {
|
|
2475
3012
|
const effectiveNow = resolveNow(now);
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
3013
|
+
db.prepare(
|
|
3014
|
+
"UPDATE repeatable_quest_tags SET deleted_at = ? WHERE repeatable_quest_id = ? AND deleted_at IS NULL"
|
|
3015
|
+
).run(effectiveNow, repeatableQuestId);
|
|
3016
|
+
const uniqueNames = [...new Set(tagNames.map((tag) => tag.trim()).filter(Boolean))];
|
|
3017
|
+
for (const name of uniqueNames) {
|
|
3018
|
+
const tagId = getOrCreateTagId(db, name, effectiveNow);
|
|
2482
3019
|
db.prepare(
|
|
2483
|
-
`INSERT INTO
|
|
3020
|
+
`INSERT INTO repeatable_quest_tags (repeatable_quest_id, tag_id, created_at)
|
|
2484
3021
|
VALUES (?, ?, ?)`
|
|
2485
|
-
).run(
|
|
3022
|
+
).run(repeatableQuestId, tagId, effectiveNow);
|
|
2486
3023
|
}
|
|
2487
3024
|
}
|
|
2488
|
-
var
|
|
2489
|
-
"src/tags/
|
|
3025
|
+
var init_replace_active_repeatable_quest_tags = __esm({
|
|
3026
|
+
"src/tags/replace_active_repeatable_quest_tags.ts"() {
|
|
2490
3027
|
init_resolve_now();
|
|
3028
|
+
init_normalize_tag_name();
|
|
2491
3029
|
}
|
|
2492
3030
|
});
|
|
2493
3031
|
|
|
2494
|
-
// src/repeatable_quests/
|
|
2495
|
-
function
|
|
3032
|
+
// src/repeatable_quests/create_repeatable_quest.ts
|
|
3033
|
+
function createRepeatableQuest(db, input) {
|
|
2496
3034
|
return withTransaction(db, () => {
|
|
2497
|
-
const
|
|
2498
|
-
const
|
|
2499
|
-
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2503
|
-
questline_id,
|
|
2504
|
-
source_rumor_id,
|
|
2505
|
-
spawned_by_quest_id,
|
|
2506
|
-
spawned_from_repeatable_id,
|
|
2507
|
-
spawned_for_at,
|
|
2508
|
-
title,
|
|
2509
|
-
objective,
|
|
2510
|
-
created_at,
|
|
2511
|
-
updated_at,
|
|
2512
|
-
effort_seconds,
|
|
2513
|
-
estimate_seconds,
|
|
2514
|
-
not_before_at,
|
|
2515
|
-
due_at,
|
|
2516
|
-
scheduled_start_at,
|
|
2517
|
-
scheduled_end_at,
|
|
2518
|
-
all_day
|
|
2519
|
-
) VALUES (?, NULL, NULL, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, ?)`
|
|
2520
|
-
).run(
|
|
2521
|
-
repeatable.questlineId ?? null,
|
|
2522
|
-
repeatable.id,
|
|
2523
|
-
anchor.anchorAt,
|
|
2524
|
-
repeatable.title,
|
|
2525
|
-
repeatable.objective,
|
|
2526
|
-
now,
|
|
2527
|
-
now,
|
|
2528
|
-
repeatable.estimateSeconds ?? null,
|
|
2529
|
-
repeatable.notBeforeOffsetSeconds == null ? null : anchor.anchorAt + repeatable.notBeforeOffsetSeconds * 1e3,
|
|
2530
|
-
repeatable.dueOffsetSeconds == null ? null : anchor.anchorAt + repeatable.dueOffsetSeconds * 1e3,
|
|
2531
|
-
repeatable.scheduledStartOffsetSeconds == null ? null : anchor.anchorAt + repeatable.scheduledStartOffsetSeconds * 1e3,
|
|
2532
|
-
repeatable.scheduledEndOffsetSeconds == null ? null : anchor.anchorAt + repeatable.scheduledEndOffsetSeconds * 1e3,
|
|
2533
|
-
repeatable.allDay ? 1 : 0
|
|
2534
|
-
);
|
|
2535
|
-
const questId = Number(created.lastInsertRowid);
|
|
2536
|
-
copyRepeatableQuestTagsToQuest(db, repeatable.id, questId, now);
|
|
2537
|
-
copyRepeatableQuestRewardsToQuest(db, repeatable.id, questId, now);
|
|
2538
|
-
quests.push(getQuestOrThrow(db, questId));
|
|
3035
|
+
const now = resolveNow(input.now);
|
|
3036
|
+
const title = assertNonEmpty(input.title, "Repeatable quest title");
|
|
3037
|
+
const objective = assertNonEmpty(input.objective, "Repeatable quest objective");
|
|
3038
|
+
parseRRule(input.rrule);
|
|
3039
|
+
if (input.questlineId != null) {
|
|
3040
|
+
assertActiveRowExists(db, "questlines", input.questlineId, "Questline");
|
|
2539
3041
|
}
|
|
2540
|
-
|
|
3042
|
+
const created = db.prepare(
|
|
3043
|
+
`INSERT INTO repeatable_quests (
|
|
3044
|
+
questline_id,
|
|
3045
|
+
title,
|
|
3046
|
+
objective,
|
|
3047
|
+
rrule,
|
|
3048
|
+
anchor_at,
|
|
3049
|
+
not_before_offset_seconds,
|
|
3050
|
+
due_offset_seconds,
|
|
3051
|
+
scheduled_start_offset_seconds,
|
|
3052
|
+
scheduled_end_offset_seconds,
|
|
3053
|
+
all_day,
|
|
3054
|
+
estimate_seconds,
|
|
3055
|
+
created_at,
|
|
3056
|
+
updated_at
|
|
3057
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
|
3058
|
+
).run(
|
|
3059
|
+
input.questlineId ?? null,
|
|
3060
|
+
title,
|
|
3061
|
+
objective,
|
|
3062
|
+
input.rrule,
|
|
3063
|
+
input.anchorAt,
|
|
3064
|
+
assertNonNegativeSeconds(input.notBeforeOffsetSeconds, "Repeatable not-before offset") ?? null,
|
|
3065
|
+
assertNonNegativeSeconds(input.dueOffsetSeconds, "Repeatable due offset") ?? null,
|
|
3066
|
+
input.scheduledStartOffsetSeconds ?? null,
|
|
3067
|
+
input.scheduledEndOffsetSeconds ?? null,
|
|
3068
|
+
input.allDay ? 1 : 0,
|
|
3069
|
+
assertNonNegativeSeconds(input.estimateSeconds, "Repeatable estimate") ?? null,
|
|
3070
|
+
now,
|
|
3071
|
+
now
|
|
3072
|
+
);
|
|
3073
|
+
const repeatableQuestId = Number(created.lastInsertRowid);
|
|
3074
|
+
if (input.tags && input.tags.length > 0) {
|
|
3075
|
+
replaceActiveRepeatableQuestTags(db, repeatableQuestId, input.tags, now);
|
|
3076
|
+
}
|
|
3077
|
+
if (input.rewards && input.rewards.length > 0) {
|
|
3078
|
+
replaceActiveRepeatableQuestRewards(db, repeatableQuestId, input.rewards, now);
|
|
3079
|
+
}
|
|
3080
|
+
return getRepeatableQuestOrThrow(db, repeatableQuestId);
|
|
2541
3081
|
});
|
|
2542
3082
|
}
|
|
2543
|
-
var
|
|
2544
|
-
"src/repeatable_quests/
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
3083
|
+
var init_create_repeatable_quest = __esm({
|
|
3084
|
+
"src/repeatable_quests/create_repeatable_quest.ts"() {
|
|
3085
|
+
init_assert_active_row_exists();
|
|
3086
|
+
init_assert_non_empty();
|
|
3087
|
+
init_assert_non_negative_seconds();
|
|
3088
|
+
init_resolve_now();
|
|
3089
|
+
init_replace_active_repeatable_quest_rewards();
|
|
3090
|
+
init_replace_active_repeatable_quest_tags();
|
|
2548
3091
|
init_with_transaction();
|
|
2549
3092
|
init_get_repeatable_quest_or_throw();
|
|
2550
|
-
|
|
3093
|
+
init_parse_rrule();
|
|
3094
|
+
}
|
|
3095
|
+
});
|
|
3096
|
+
|
|
3097
|
+
// src/repeatable_quests/soft_delete_repeatable_quest.ts
|
|
3098
|
+
function softDeleteRepeatableQuest(db, repeatableQuestId, deletedAt) {
|
|
3099
|
+
const now = resolveNow(deletedAt);
|
|
3100
|
+
getRepeatableQuestOrThrow(db, repeatableQuestId);
|
|
3101
|
+
db.prepare(
|
|
3102
|
+
`UPDATE repeatable_quests
|
|
3103
|
+
SET deleted_at = ?, updated_at = ?
|
|
3104
|
+
WHERE id = ? AND deleted_at IS NULL`
|
|
3105
|
+
).run(now, now, repeatableQuestId);
|
|
3106
|
+
}
|
|
3107
|
+
var init_soft_delete_repeatable_quest = __esm({
|
|
3108
|
+
"src/repeatable_quests/soft_delete_repeatable_quest.ts"() {
|
|
3109
|
+
init_resolve_now();
|
|
3110
|
+
init_get_repeatable_quest_or_throw();
|
|
2551
3111
|
}
|
|
2552
3112
|
});
|
|
2553
3113
|
|
|
@@ -4117,7 +4677,7 @@ function validateReviewInput(input) {
|
|
|
4117
4677
|
'Use `filters.dayAt` only with `view: "quests.scheduled_for_day"`.'
|
|
4118
4678
|
);
|
|
4119
4679
|
}
|
|
4120
|
-
if ((input.view === "rumors" || input.view === "questlines") && hasQuestFilters(input.filters)) {
|
|
4680
|
+
if ((input.view === "rumors" || input.view === "questlines" || input.view === "deliveries") && hasQuestFilters(input.filters)) {
|
|
4121
4681
|
return toolFailure(
|
|
4122
4682
|
"protocol",
|
|
4123
4683
|
"invalid_input",
|
|
@@ -4185,6 +4745,15 @@ function reviewQuestlogToolHandler(db, input) {
|
|
|
4185
4745
|
if (invalid) {
|
|
4186
4746
|
return invalid;
|
|
4187
4747
|
}
|
|
4748
|
+
if (input.view === "deliveries")
|
|
4749
|
+
return toolSuccess("Pending and failed deliveries.", {
|
|
4750
|
+
view: input.view,
|
|
4751
|
+
items: [],
|
|
4752
|
+
deliveries: listDeliveries(db),
|
|
4753
|
+
count: listDeliveries(db).length,
|
|
4754
|
+
appliedFilters: input.filters,
|
|
4755
|
+
evaluatedAt: input.now ?? Date.now()
|
|
4756
|
+
});
|
|
4188
4757
|
const now = input.now ?? Date.now();
|
|
4189
4758
|
let items = [];
|
|
4190
4759
|
switch (input.view) {
|
|
@@ -4229,6 +4798,7 @@ function reviewQuestlogToolHandler(db, input) {
|
|
|
4229
4798
|
var reviewQuestlogTool;
|
|
4230
4799
|
var init_review_questlog_tool = __esm({
|
|
4231
4800
|
"src/tools/review_questlog_tool.ts"() {
|
|
4801
|
+
init_list_deliveries();
|
|
4232
4802
|
init_list_questlines();
|
|
4233
4803
|
init_quests();
|
|
4234
4804
|
init_get_repeatable_quest_or_throw();
|
|
@@ -4257,6 +4827,7 @@ var init_review_questlog_tool = __esm({
|
|
|
4257
4827
|
inputSchema: objectSchema(
|
|
4258
4828
|
{
|
|
4259
4829
|
view: enumSchema("Which operational review surface to return.", [
|
|
4830
|
+
"deliveries",
|
|
4260
4831
|
"questlines",
|
|
4261
4832
|
"quests.active",
|
|
4262
4833
|
"quests.available",
|
|
@@ -4599,7 +5170,7 @@ var init_reward_work_tool = __esm({
|
|
|
4599
5170
|
name: stringSchema("Reward name."),
|
|
4600
5171
|
now: integerSchema("Optional update timestamp for the update action.")
|
|
4601
5172
|
},
|
|
4602
|
-
[]
|
|
5173
|
+
["kind", "name"]
|
|
4603
5174
|
),
|
|
4604
5175
|
rewards: arraySchema(
|
|
4605
5176
|
objectSchema(
|
|
@@ -5513,7 +6084,7 @@ var init_shape_work_tool = __esm({
|
|
|
5513
6084
|
title: stringSchema("Questline title."),
|
|
5514
6085
|
description: stringSchema("Optional questline description.")
|
|
5515
6086
|
},
|
|
5516
|
-
[]
|
|
6087
|
+
["title"]
|
|
5517
6088
|
),
|
|
5518
6089
|
rumorId: integerSchema("Rumor to settle, dismiss, or reopen."),
|
|
5519
6090
|
dismissedAt: integerSchema("Optional dismissal timestamp."),
|
|
@@ -5827,6 +6398,7 @@ var init_tool_registry = __esm({
|
|
|
5827
6398
|
"src/tools/tool_registry.ts"() {
|
|
5828
6399
|
init_capture_rumor_tool();
|
|
5829
6400
|
init_inspect_questlog_item_tool();
|
|
6401
|
+
init_manage_hooks_tool();
|
|
5830
6402
|
init_manage_repeatable_tool();
|
|
5831
6403
|
init_organize_work_tool();
|
|
5832
6404
|
init_plan_quest_tool();
|
|
@@ -5838,6 +6410,7 @@ var init_tool_registry = __esm({
|
|
|
5838
6410
|
init_shape_work_tool();
|
|
5839
6411
|
init_tag_work_tool();
|
|
5840
6412
|
questlogTools = [
|
|
6413
|
+
manageHooksTool,
|
|
5841
6414
|
searchQuestlogTool,
|
|
5842
6415
|
reviewQuestlogTool,
|
|
5843
6416
|
inspectQuestlogItemTool,
|
|
@@ -5867,7 +6440,7 @@ var init_questlog_surface_tools = __esm({
|
|
|
5867
6440
|
whenNotToUse: tool.whenNotToUse ?? "",
|
|
5868
6441
|
readOnly: tool.readOnly,
|
|
5869
6442
|
sideEffects: tool.sideEffects,
|
|
5870
|
-
inputSchema:
|
|
6443
|
+
inputSchema: tool.inputSchema,
|
|
5871
6444
|
handler: tool.handler
|
|
5872
6445
|
})
|
|
5873
6446
|
);
|