@grindxp/cli 0.1.8 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +766 -134
- package/dist/web/server/assets/{agent.functions-zpMkBrG3.js → agent.functions-BL3upUNr.js} +444 -47
- package/dist/web/server/assets/{data.functions-9hSsMFx_.js → data.functions-DZmdFOMQ.js} +2 -2
- package/dist/web/server/assets/{index-C09LXa7Z.js → index-B2ULpkv2.js} +3 -3
- package/dist/web/server/assets/{index-D31yYLCV.js → index-BGBMycx-.js} +3 -3
- package/dist/web/server/assets/{index-BDL7hA7T.js → index-BQUCDamI.js} +3 -3
- package/dist/web/server/assets/{index-D7z4dRpK.js → index-CB8UtTN8.js} +2 -2
- package/dist/web/server/assets/{index-CJ_-TSqN.js → index-DTB2dYCz.js} +2 -2
- package/dist/web/server/assets/{index-D2fMUSdJ.js → index-DfU25rnD.js} +2 -2
- package/dist/web/server/assets/{index-b30aLTKp.js → index-SHH7zSKt.js} +2 -2
- package/dist/web/server/assets/{router-1koL9I3U.js → router-CXyGzWDS.js} +5 -5
- package/dist/web/server/assets/{sessions-DOkG47Ex.js → sessions-UCWtijHE.js} +47 -12
- package/dist/web/server/assets/{token-W0NPKas8.js → token-DGoahKjI.js} +3 -3
- package/dist/web/server/assets/{token-util-DA5xS0pj.js → token-util-BopJPy-I.js} +1 -1
- package/dist/web/server/assets/{token-util-1cB5CD6M.js → token-util-Bw35afYM.js} +2 -2
- package/dist/web/server/assets/{vault.server-Ndu49yTf.js → vault.server-CscY5Z8e.js} +46 -45
- package/dist/web/server/server.js +9 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,25 +5,43 @@ var __getProtoOf = Object.getPrototypeOf;
|
|
|
5
5
|
var __defProp = Object.defineProperty;
|
|
6
6
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
function __accessProp(key) {
|
|
9
|
+
return this[key];
|
|
10
|
+
}
|
|
11
|
+
var __toESMCache_node;
|
|
12
|
+
var __toESMCache_esm;
|
|
8
13
|
var __toESM = (mod, isNodeMode, target) => {
|
|
14
|
+
var canCache = mod != null && typeof mod === "object";
|
|
15
|
+
if (canCache) {
|
|
16
|
+
var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
|
|
17
|
+
var cached = cache.get(mod);
|
|
18
|
+
if (cached)
|
|
19
|
+
return cached;
|
|
20
|
+
}
|
|
9
21
|
target = mod != null ? __create(__getProtoOf(mod)) : {};
|
|
10
22
|
const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
|
|
11
23
|
for (let key of __getOwnPropNames(mod))
|
|
12
24
|
if (!__hasOwnProp.call(to, key))
|
|
13
25
|
__defProp(to, key, {
|
|
14
|
-
get: (
|
|
26
|
+
get: __accessProp.bind(mod, key),
|
|
15
27
|
enumerable: true
|
|
16
28
|
});
|
|
29
|
+
if (canCache)
|
|
30
|
+
cache.set(mod, to);
|
|
17
31
|
return to;
|
|
18
32
|
};
|
|
19
33
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
34
|
+
var __returnValue = (v) => v;
|
|
35
|
+
function __exportSetter(name, newValue) {
|
|
36
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
37
|
+
}
|
|
20
38
|
var __export = (target, all) => {
|
|
21
39
|
for (var name in all)
|
|
22
40
|
__defProp(target, name, {
|
|
23
41
|
get: all[name],
|
|
24
42
|
enumerable: true,
|
|
25
43
|
configurable: true,
|
|
26
|
-
set: (
|
|
44
|
+
set: __exportSetter.bind(all, name)
|
|
27
45
|
});
|
|
28
46
|
};
|
|
29
47
|
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
@@ -28273,8 +28291,7 @@ async function flushCompanionMemory(params) {
|
|
|
28273
28291
|
messages: [...messages, { role: "user", content: MEMORY_FLUSH_PROMPT }],
|
|
28274
28292
|
tools,
|
|
28275
28293
|
activeTools: ["list_insights", "store_insight", "update_user_context"],
|
|
28276
|
-
stopWhen: stepCountIs(
|
|
28277
|
-
maxOutputTokens: 128,
|
|
28294
|
+
stopWhen: stepCountIs(6),
|
|
28278
28295
|
maxRetries: 1,
|
|
28279
28296
|
...abortSignal ? { abortSignal } : {}
|
|
28280
28297
|
});
|
|
@@ -28332,15 +28349,16 @@ What was planned or in progress.
|
|
|
28332
28349
|
|
|
28333
28350
|
Be concise. This summary replaces the full conversation history.`, MEMORY_FLUSH_PROMPT = `Session is nearing compaction.
|
|
28334
28351
|
|
|
28335
|
-
|
|
28336
|
-
|
|
28337
|
-
|
|
28352
|
+
Step 1: Call list_insights to see what is already stored.
|
|
28353
|
+
Step 2: Identify any durable facts from this session that are NOT already covered by existing insights.
|
|
28354
|
+
Step 3: Only call store_insight for genuinely new information. Do not re-store facts already captured \u2014 store_insight will merge exact duplicates but semantic duplicates waste space.
|
|
28355
|
+
Step 4: If everything worth keeping is already stored, reply exactly: NO_REPLY
|
|
28338
28356
|
|
|
28339
28357
|
Rules:
|
|
28340
28358
|
- Store only durable information likely to matter in future sessions.
|
|
28341
28359
|
- Do not store transient chatter.
|
|
28342
28360
|
- Keep insights short and factual.
|
|
28343
|
-
-
|
|
28361
|
+
- Prefer updating an existing insight over creating a new one when the content overlaps.`;
|
|
28344
28362
|
var init_compaction = __esm(() => {
|
|
28345
28363
|
init_dist5();
|
|
28346
28364
|
});
|
|
@@ -40820,8 +40838,16 @@ TOOL USAGE:
|
|
|
40820
40838
|
- When asked whether integrations/channels are connected or available (Telegram, WhatsApp, Discord, Google Calendar), call get_integrations_status first. Do not guess.
|
|
40821
40839
|
- If the user asks to send or test a Telegram message, call send_telegram_message immediately. Never ask the user for their chat ID \u2014 it is resolved automatically.
|
|
40822
40840
|
- If send_telegram_message fails because no chat ID was found yet, tell the user to send any message to the bot from Telegram (not /start specifically) and offer to try again immediately after.
|
|
40823
|
-
- When the user asks to automate, schedule reminders, or set recurring workflows, use forge tools
|
|
40824
|
-
- Before updating or
|
|
40841
|
+
- When the user asks to automate, schedule reminders, or set recurring workflows, use forge tools directly \u2014 never tell the user to use the CLI manually.
|
|
40842
|
+
- Before updating, deleting, or running a specific rule, call list_forge_rules to confirm the target and read its xpImpact field.
|
|
40843
|
+
- xpImpact: false rules (notifications, reminders, monitors): act fully autonomously \u2014 no explanation needed beyond confirming what you did.
|
|
40844
|
+
- xpImpact: true rules (log-to-vault, update-skill): proceed autonomously and briefly mention in your reply that XP will be awarded automatically.
|
|
40845
|
+
- Deleting a rule is permanent \u2014 tell the user this before calling delete_forge_rule.
|
|
40846
|
+
- run-script rules execute shell scripts as automations \u2014 always show the full script in your reply when creating or updating one.
|
|
40847
|
+
- Use list_forge_runs to diagnose failures.
|
|
40848
|
+
- When the user names a specific calendar (anything other than 'primary'), always call list_calendars first to resolve the name to its id, then pass that id to create_calendar_event or get_calendar_events. Never assume the id \u2014 always look it up.
|
|
40849
|
+
- If list_calendars does not return the named calendar and the user wants to create it, call create_calendar first, then use the returned id immediately for any subsequent event creation.
|
|
40850
|
+
- Never ask the user for a calendar ID \u2014 always resolve it yourself via list_calendars.
|
|
40825
40851
|
- Keep responses concise. 1-3 sentences for simple actions. No walls of text.
|
|
40826
40852
|
|
|
40827
40853
|
WEB & FILE ACCESS:
|
|
@@ -42711,6 +42737,16 @@ async function listCalendars(serviceConfig) {
|
|
|
42711
42737
|
const data = await resp.json();
|
|
42712
42738
|
return data.items ?? [];
|
|
42713
42739
|
}
|
|
42740
|
+
async function createCalendar(serviceConfig, summary, timeZone) {
|
|
42741
|
+
const body = { summary };
|
|
42742
|
+
if (timeZone)
|
|
42743
|
+
body.timeZone = timeZone;
|
|
42744
|
+
const resp = await googleFetch(`${BASE}/calendars`, serviceConfig, {
|
|
42745
|
+
method: "POST",
|
|
42746
|
+
body: JSON.stringify(body)
|
|
42747
|
+
});
|
|
42748
|
+
return resp.json();
|
|
42749
|
+
}
|
|
42714
42750
|
function getEventDateKey(event, tz) {
|
|
42715
42751
|
if (event.start.date)
|
|
42716
42752
|
return event.start.date;
|
|
@@ -46056,6 +46092,12 @@ async function findCompanionInsightByContent(db2, userId, category, content) {
|
|
|
46056
46092
|
});
|
|
46057
46093
|
return row ?? null;
|
|
46058
46094
|
}
|
|
46095
|
+
async function updateCompanionMode(db2, userId, mode) {
|
|
46096
|
+
const [updated] = await db2.update(companionSettings).set({ mode, updatedAt: Date.now() }).where(eq(companionSettings.userId, userId)).returning();
|
|
46097
|
+
if (!updated)
|
|
46098
|
+
throw new Error("Companion not found. Run `grindxp init` first.");
|
|
46099
|
+
return updated;
|
|
46100
|
+
}
|
|
46059
46101
|
async function deleteCompanionInsight(db2, insightId, userId) {
|
|
46060
46102
|
const row = await db2.delete(companionInsights).where(and(eq(companionInsights.id, insightId), eq(companionInsights.userId, userId))).returning({ id: companionInsights.id });
|
|
46061
46103
|
return row.length > 0;
|
|
@@ -46176,6 +46218,16 @@ async function deleteForgeRule(db2, userId, ruleId) {
|
|
|
46176
46218
|
const rows = await db2.delete(forgeRules).where(and(eq(forgeRules.id, ruleId), eq(forgeRules.userId, userId))).returning({ id: forgeRules.id });
|
|
46177
46219
|
return rows.length > 0;
|
|
46178
46220
|
}
|
|
46221
|
+
async function listSignals(db2, userId, options = {}) {
|
|
46222
|
+
const { limit = 20, source } = options;
|
|
46223
|
+
const where = source ? and(eq(signals.userId, userId), eq(signals.source, source)) : eq(signals.userId, userId);
|
|
46224
|
+
const rows = await db2.query.signals.findMany({
|
|
46225
|
+
where,
|
|
46226
|
+
orderBy: [desc(signals.detectedAt)],
|
|
46227
|
+
limit
|
|
46228
|
+
});
|
|
46229
|
+
return rows.map(rowToSignal);
|
|
46230
|
+
}
|
|
46179
46231
|
async function recordSignal(db2, input) {
|
|
46180
46232
|
const valid = createSignalInputSchema.parse(input);
|
|
46181
46233
|
const [row] = await db2.insert(signals).values(valid).returning();
|
|
@@ -46483,6 +46535,36 @@ async function completeQuest(db2, input) {
|
|
|
46483
46535
|
});
|
|
46484
46536
|
return { xpEarned: xpResult.totalXp, skillGains };
|
|
46485
46537
|
}
|
|
46538
|
+
async function updateQuest(db2, questId, userId, patch) {
|
|
46539
|
+
const set2 = { updatedAt: Date.now() };
|
|
46540
|
+
if (patch.title !== undefined)
|
|
46541
|
+
set2.title = patch.title;
|
|
46542
|
+
if (patch.description !== undefined)
|
|
46543
|
+
set2.description = patch.description;
|
|
46544
|
+
if (patch.type !== undefined)
|
|
46545
|
+
set2.type = patch.type;
|
|
46546
|
+
if (patch.difficulty !== undefined)
|
|
46547
|
+
set2.difficulty = patch.difficulty;
|
|
46548
|
+
if (patch.skillTags !== undefined)
|
|
46549
|
+
set2.skillTags = patch.skillTags;
|
|
46550
|
+
if (patch.baseXp !== undefined)
|
|
46551
|
+
set2.baseXp = patch.baseXp;
|
|
46552
|
+
if (patch.scheduleCron !== undefined)
|
|
46553
|
+
set2.scheduleCron = patch.scheduleCron;
|
|
46554
|
+
if (patch.deadlineAt !== undefined)
|
|
46555
|
+
set2.deadlineAt = patch.deadlineAt;
|
|
46556
|
+
const [row] = await db2.update(quests).set(set2).where(and(eq(quests.id, questId), eq(quests.userId, userId))).returning();
|
|
46557
|
+
return row ? rowToQuest(row) : null;
|
|
46558
|
+
}
|
|
46559
|
+
async function listQuestLogs(db2, userId, options = {}) {
|
|
46560
|
+
const { limit = 20, since } = options;
|
|
46561
|
+
const where = since ? and(eq(questLogs.userId, userId), gte(questLogs.completedAt, since)) : eq(questLogs.userId, userId);
|
|
46562
|
+
return db2.query.questLogs.findMany({
|
|
46563
|
+
where,
|
|
46564
|
+
orderBy: [desc(questLogs.completedAt)],
|
|
46565
|
+
limit
|
|
46566
|
+
});
|
|
46567
|
+
}
|
|
46486
46568
|
var init_quests = __esm(() => {
|
|
46487
46569
|
init_drizzle_orm();
|
|
46488
46570
|
init_schema();
|
|
@@ -46554,6 +46636,7 @@ var init_repositories = __esm(() => {
|
|
|
46554
46636
|
// ../core/src/forge/runtime.ts
|
|
46555
46637
|
import { spawnSync } from "child_process";
|
|
46556
46638
|
import { statSync } from "fs";
|
|
46639
|
+
import { homedir as homedir2 } from "os";
|
|
46557
46640
|
import { isAbsolute, resolve as resolve2 } from "path";
|
|
46558
46641
|
async function runForgeTick(options) {
|
|
46559
46642
|
const at3 = options.at ?? Date.now();
|
|
@@ -46719,6 +46802,8 @@ async function executeForgeAction(db2, userId, plan) {
|
|
|
46719
46802
|
return executeLogToVault(db2, userId, plan);
|
|
46720
46803
|
case "send-notification":
|
|
46721
46804
|
return executeSendNotification(plan);
|
|
46805
|
+
case "run-script":
|
|
46806
|
+
return executeRunScript(plan);
|
|
46722
46807
|
default:
|
|
46723
46808
|
return {
|
|
46724
46809
|
status: "skipped",
|
|
@@ -46911,6 +46996,53 @@ async function executeSendNotification(plan) {
|
|
|
46911
46996
|
error: `Unsupported notification channel '${channel}'.`
|
|
46912
46997
|
};
|
|
46913
46998
|
}
|
|
46999
|
+
async function executeRunScript(plan) {
|
|
47000
|
+
const script = asString2(plan.actionConfig.script);
|
|
47001
|
+
if (!script) {
|
|
47002
|
+
return {
|
|
47003
|
+
status: "failed",
|
|
47004
|
+
actionPayload: {},
|
|
47005
|
+
error: "run-script requires actionConfig.script."
|
|
47006
|
+
};
|
|
47007
|
+
}
|
|
47008
|
+
const timeoutMs = parsePositiveInt(plan.actionConfig.timeout) ?? 30000;
|
|
47009
|
+
const rawWorkdir = asString2(plan.actionConfig.workdir);
|
|
47010
|
+
const workdir = rawWorkdir ? rawWorkdir.replace(/^~/, homedir2()) : undefined;
|
|
47011
|
+
const result = spawnSync("sh", ["-c", script], {
|
|
47012
|
+
encoding: "utf8",
|
|
47013
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
47014
|
+
timeout: timeoutMs,
|
|
47015
|
+
...workdir ? { cwd: workdir } : {}
|
|
47016
|
+
});
|
|
47017
|
+
const errCode = result.error?.code;
|
|
47018
|
+
if (errCode === "ENOENT" && workdir) {
|
|
47019
|
+
return {
|
|
47020
|
+
status: "failed",
|
|
47021
|
+
actionPayload: { script, exitCode: null },
|
|
47022
|
+
error: `Working directory does not exist: ${workdir}`
|
|
47023
|
+
};
|
|
47024
|
+
}
|
|
47025
|
+
if (errCode === "ETIMEDOUT") {
|
|
47026
|
+
return {
|
|
47027
|
+
status: "failed",
|
|
47028
|
+
actionPayload: { script, exitCode: null },
|
|
47029
|
+
error: `Script timed out after ${timeoutMs}ms.`
|
|
47030
|
+
};
|
|
47031
|
+
}
|
|
47032
|
+
if (result.status !== 0) {
|
|
47033
|
+
const stderr = result.stderr?.trim().slice(0, 500) ?? "";
|
|
47034
|
+
return {
|
|
47035
|
+
status: "failed",
|
|
47036
|
+
actionPayload: { script, exitCode: result.status },
|
|
47037
|
+
error: `Script exited ${result.status ?? "null"}${stderr ? `: ${stderr}` : ""}`
|
|
47038
|
+
};
|
|
47039
|
+
}
|
|
47040
|
+
const stdout = result.stdout?.trim().slice(0, 2000) ?? "";
|
|
47041
|
+
return {
|
|
47042
|
+
status: "success",
|
|
47043
|
+
actionPayload: { script, exitCode: 0, ...stdout ? { stdout } : {} }
|
|
47044
|
+
};
|
|
47045
|
+
}
|
|
46914
47046
|
async function executeQueueQuest(db2, userId, plan) {
|
|
46915
47047
|
const eventPayload = asRecord(plan.actionConfig.eventPayload);
|
|
46916
47048
|
const questId = asString2(plan.actionConfig.questId) ?? asString2(eventPayload?.questId) ?? asString2(plan.actionConfig.targetQuestId);
|
|
@@ -47589,6 +47721,21 @@ var init_google = __esm(() => {
|
|
|
47589
47721
|
import * as fs from "fs/promises";
|
|
47590
47722
|
import * as path from "path";
|
|
47591
47723
|
import os from "os";
|
|
47724
|
+
function requireTrust(ctx, toolName) {
|
|
47725
|
+
const required2 = TOOL_TRUST_REQUIREMENTS[toolName];
|
|
47726
|
+
if (required2 === undefined)
|
|
47727
|
+
return { denied: false };
|
|
47728
|
+
const current = ctx.trustLevel ?? 0;
|
|
47729
|
+
if (current < required2) {
|
|
47730
|
+
const requiredName = TRUST_LEVEL_NAMES[required2] ?? `Lv.${required2}`;
|
|
47731
|
+
const currentName = TRUST_LEVEL_NAMES[current] ?? `Lv.${current}`;
|
|
47732
|
+
return {
|
|
47733
|
+
denied: true,
|
|
47734
|
+
error: `Action requires trust level ${required2} (${requiredName}). Current level: ${current} (${currentName}). Grant trust with: grindxp companion trust ${required2}`
|
|
47735
|
+
};
|
|
47736
|
+
}
|
|
47737
|
+
return { denied: false };
|
|
47738
|
+
}
|
|
47592
47739
|
function expandPath(p) {
|
|
47593
47740
|
if (p.startsWith("~/"))
|
|
47594
47741
|
return path.join(os.homedir(), p.slice(2));
|
|
@@ -47646,6 +47793,38 @@ async function requirePermission(ctx, toolName, detail) {
|
|
|
47646
47793
|
return { denied: true, error: "Permission denied by user" };
|
|
47647
47794
|
return { denied: false };
|
|
47648
47795
|
}
|
|
47796
|
+
function classifyGoogleError(err) {
|
|
47797
|
+
if (err instanceof GoogleNotConnectedError || err instanceof GoogleTokenExpiredError) {
|
|
47798
|
+
return "Google account not connected or session expired. Run `grindxp integrations connect google`.";
|
|
47799
|
+
}
|
|
47800
|
+
if (err instanceof GoogleApiError) {
|
|
47801
|
+
switch (err.status) {
|
|
47802
|
+
case 401:
|
|
47803
|
+
return "Google account disconnected. Run `grindxp integrations connect google`.";
|
|
47804
|
+
case 403:
|
|
47805
|
+
return "No write access to this calendar. Check the calendar's sharing settings.";
|
|
47806
|
+
case 404:
|
|
47807
|
+
return "Calendar or event not found. Use list_calendars to verify available calendar IDs.";
|
|
47808
|
+
case 409:
|
|
47809
|
+
return "Conflict \u2014 this event may already exist on the calendar.";
|
|
47810
|
+
case 410:
|
|
47811
|
+
return "Sync token expired \u2014 a full re-sync will happen on the next poll.";
|
|
47812
|
+
}
|
|
47813
|
+
if (err.status >= 500) {
|
|
47814
|
+
return "Google Calendar is temporarily unavailable. Try again in a moment.";
|
|
47815
|
+
}
|
|
47816
|
+
if (err.status === 400) {
|
|
47817
|
+
try {
|
|
47818
|
+
const parsed = JSON.parse(err.body);
|
|
47819
|
+
const msg = parsed?.error?.message;
|
|
47820
|
+
if (msg)
|
|
47821
|
+
return `Invalid request: ${msg}`;
|
|
47822
|
+
} catch {}
|
|
47823
|
+
return "Invalid request \u2014 check the calendar ID and date format.";
|
|
47824
|
+
}
|
|
47825
|
+
}
|
|
47826
|
+
return err instanceof Error ? err.message : String(err);
|
|
47827
|
+
}
|
|
47649
47828
|
async function extractText(html) {
|
|
47650
47829
|
let text4 = "";
|
|
47651
47830
|
let skip = false;
|
|
@@ -47826,14 +48005,6 @@ async function resolveTelegramChatId(ctx, token) {
|
|
|
47826
48005
|
return { chatId: candidate, source: "recent-signal" };
|
|
47827
48006
|
}
|
|
47828
48007
|
}
|
|
47829
|
-
const webhookActive = Boolean(ctx.config?.gateway?.telegramWebhookSecret ?? freshConfig?.gateway?.telegramWebhookSecret);
|
|
47830
|
-
if (webhookActive) {
|
|
47831
|
-
return {
|
|
47832
|
-
chatId: null,
|
|
47833
|
-
source: "none",
|
|
47834
|
-
detail: "Send any message to your Telegram bot and I'll respond automatically. The chat ID will be captured on first contact."
|
|
47835
|
-
};
|
|
47836
|
-
}
|
|
47837
48008
|
const updatesResponse = await fetch(`https://api.telegram.org/bot${token}/getUpdates?limit=50&timeout=0`, {
|
|
47838
48009
|
method: "GET"
|
|
47839
48010
|
});
|
|
@@ -47898,7 +48069,10 @@ function persistTelegramDefaultChatId(ctx, chatId) {
|
|
|
47898
48069
|
telegramDefaultChatId: chatId
|
|
47899
48070
|
}
|
|
47900
48071
|
};
|
|
47901
|
-
|
|
48072
|
+
const onDisk = readGrindConfig();
|
|
48073
|
+
if (!onDisk?.gateway)
|
|
48074
|
+
return;
|
|
48075
|
+
writeGrindConfig({ ...onDisk, gateway: { ...onDisk.gateway, telegramDefaultChatId: chatId } });
|
|
47902
48076
|
}
|
|
47903
48077
|
function asRecord2(value) {
|
|
47904
48078
|
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
@@ -48210,6 +48384,37 @@ async function normalizeForgeRuleDefinition(ctx, input) {
|
|
|
48210
48384
|
}
|
|
48211
48385
|
break;
|
|
48212
48386
|
}
|
|
48387
|
+
case "run-script": {
|
|
48388
|
+
const script = asNonEmptyString(actionConfig.script);
|
|
48389
|
+
if (!script) {
|
|
48390
|
+
return {
|
|
48391
|
+
ok: false,
|
|
48392
|
+
error: "run-script requires actionConfig.script (shell command to execute)."
|
|
48393
|
+
};
|
|
48394
|
+
}
|
|
48395
|
+
actionConfig.script = script;
|
|
48396
|
+
if (actionConfig.timeout !== undefined) {
|
|
48397
|
+
const timeout = parsePositiveInt2(actionConfig.timeout);
|
|
48398
|
+
if (!timeout) {
|
|
48399
|
+
return {
|
|
48400
|
+
ok: false,
|
|
48401
|
+
error: "run-script actionConfig.timeout must be a positive integer (milliseconds)."
|
|
48402
|
+
};
|
|
48403
|
+
}
|
|
48404
|
+
actionConfig.timeout = timeout;
|
|
48405
|
+
}
|
|
48406
|
+
if (actionConfig.workdir !== undefined) {
|
|
48407
|
+
const workdir = asNonEmptyString(actionConfig.workdir);
|
|
48408
|
+
if (!workdir) {
|
|
48409
|
+
return {
|
|
48410
|
+
ok: false,
|
|
48411
|
+
error: "run-script actionConfig.workdir must be a non-empty string when provided."
|
|
48412
|
+
};
|
|
48413
|
+
}
|
|
48414
|
+
actionConfig.workdir = workdir;
|
|
48415
|
+
}
|
|
48416
|
+
break;
|
|
48417
|
+
}
|
|
48213
48418
|
}
|
|
48214
48419
|
return {
|
|
48215
48420
|
ok: true,
|
|
@@ -48275,7 +48480,7 @@ function createGrindTools(ctx) {
|
|
|
48275
48480
|
const calendars = await listCalendars(googleConfig);
|
|
48276
48481
|
return { ok: true, calendars, count: calendars.length };
|
|
48277
48482
|
} catch (err) {
|
|
48278
|
-
return { ok: false, error:
|
|
48483
|
+
return { ok: false, error: classifyGoogleError(err) };
|
|
48279
48484
|
}
|
|
48280
48485
|
}
|
|
48281
48486
|
}),
|
|
@@ -48362,12 +48567,12 @@ function createGrindTools(ctx) {
|
|
|
48362
48567
|
});
|
|
48363
48568
|
return { ok: true, events: result.events, count: result.events.length };
|
|
48364
48569
|
} catch (err) {
|
|
48365
|
-
return { ok: false, error:
|
|
48570
|
+
return { ok: false, error: classifyGoogleError(err) };
|
|
48366
48571
|
}
|
|
48367
48572
|
}
|
|
48368
48573
|
}),
|
|
48369
48574
|
create_calendar_event: tool({
|
|
48370
|
-
description: "Create a new event in Google Calendar.
|
|
48575
|
+
description: "Create a new event in Google Calendar. " + "If the user names a specific calendar (anything other than 'primary'), call list_calendars first to resolve its id, then pass that id as calendarId. " + "If the calendar does not exist yet, call create_calendar first, then use the returned id.",
|
|
48371
48576
|
inputSchema: exports_external.object({
|
|
48372
48577
|
summary: exports_external.string().min(1).max(500).describe("Event title"),
|
|
48373
48578
|
startDateTime: exports_external.string().describe("Start time (ISO 8601, e.g. 2026-02-21T09:00:00)"),
|
|
@@ -48376,7 +48581,8 @@ function createGrindTools(ctx) {
|
|
|
48376
48581
|
location: exports_external.string().optional().describe("Event location"),
|
|
48377
48582
|
attendees: exports_external.array(exports_external.string().email()).optional().describe("List of attendee email addresses"),
|
|
48378
48583
|
allDay: exports_external.boolean().optional().describe("If true, treats startDateTime/endDateTime as dates (YYYY-MM-DD)"),
|
|
48379
|
-
timeZone: exports_external.string().optional().describe("IANA timezone name (e.g. America/New_York)")
|
|
48584
|
+
timeZone: exports_external.string().optional().describe("IANA timezone name (e.g. America/New_York)"),
|
|
48585
|
+
calendarId: exports_external.string().optional().describe("Calendar ID to create the event in. Use list_calendars to resolve a calendar name to its id. Defaults to the primary calendar.")
|
|
48380
48586
|
}),
|
|
48381
48587
|
execute: async ({
|
|
48382
48588
|
summary,
|
|
@@ -48386,7 +48592,8 @@ function createGrindTools(ctx) {
|
|
|
48386
48592
|
location,
|
|
48387
48593
|
attendees,
|
|
48388
48594
|
allDay,
|
|
48389
|
-
timeZone
|
|
48595
|
+
timeZone,
|
|
48596
|
+
calendarId
|
|
48390
48597
|
}) => {
|
|
48391
48598
|
const googleConfig = ctx.config?.services?.google;
|
|
48392
48599
|
if (!googleConfig) {
|
|
@@ -48405,10 +48612,32 @@ function createGrindTools(ctx) {
|
|
|
48405
48612
|
...attendees ? { attendees } : {},
|
|
48406
48613
|
...allDay ? { allDay } : {},
|
|
48407
48614
|
...timeZone ? { timeZone } : {}
|
|
48408
|
-
});
|
|
48615
|
+
}, calendarId ?? "primary");
|
|
48409
48616
|
return { ok: true, event };
|
|
48410
48617
|
} catch (err) {
|
|
48411
|
-
return { ok: false, error:
|
|
48618
|
+
return { ok: false, error: classifyGoogleError(err) };
|
|
48619
|
+
}
|
|
48620
|
+
}
|
|
48621
|
+
}),
|
|
48622
|
+
create_calendar: tool({
|
|
48623
|
+
description: "Create a new Google Calendar. After creation, use the returned id with create_calendar_event to add events to it. " + "Use this when the user asks to create a calendar that does not yet exist in their list.",
|
|
48624
|
+
inputSchema: exports_external.object({
|
|
48625
|
+
summary: exports_external.string().min(1).max(255).describe("Calendar name (e.g. 'Work', 'God', 'Study')"),
|
|
48626
|
+
timeZone: exports_external.string().optional().describe("IANA timezone name for the calendar (e.g. America/Sao_Paulo)")
|
|
48627
|
+
}),
|
|
48628
|
+
execute: async ({ summary, timeZone }) => {
|
|
48629
|
+
const googleConfig = ctx.config?.services?.google;
|
|
48630
|
+
if (!googleConfig) {
|
|
48631
|
+
return {
|
|
48632
|
+
ok: false,
|
|
48633
|
+
error: "Google account not connected. Run `grindxp integrations connect google`."
|
|
48634
|
+
};
|
|
48635
|
+
}
|
|
48636
|
+
try {
|
|
48637
|
+
const calendar2 = await createCalendar(googleConfig, summary, timeZone);
|
|
48638
|
+
return { ok: true, calendar: calendar2 };
|
|
48639
|
+
} catch (err) {
|
|
48640
|
+
return { ok: false, error: classifyGoogleError(err) };
|
|
48412
48641
|
}
|
|
48413
48642
|
}
|
|
48414
48643
|
}),
|
|
@@ -48456,7 +48685,7 @@ function createGrindTools(ctx) {
|
|
|
48456
48685
|
const event = await updateCalendarEvent(googleConfig, eventId, patch, calendarId ?? "primary");
|
|
48457
48686
|
return { ok: true, event };
|
|
48458
48687
|
} catch (err) {
|
|
48459
|
-
return { ok: false, error:
|
|
48688
|
+
return { ok: false, error: classifyGoogleError(err) };
|
|
48460
48689
|
}
|
|
48461
48690
|
}
|
|
48462
48691
|
}),
|
|
@@ -48478,7 +48707,7 @@ function createGrindTools(ctx) {
|
|
|
48478
48707
|
await deleteCalendarEvent(googleConfig, eventId, calendarId ?? "primary");
|
|
48479
48708
|
return { ok: true, eventId };
|
|
48480
48709
|
} catch (err) {
|
|
48481
|
-
return { ok: false, error:
|
|
48710
|
+
return { ok: false, error: classifyGoogleError(err) };
|
|
48482
48711
|
}
|
|
48483
48712
|
}
|
|
48484
48713
|
}),
|
|
@@ -48593,7 +48822,7 @@ function createGrindTools(ctx) {
|
|
|
48593
48822
|
}
|
|
48594
48823
|
}),
|
|
48595
48824
|
list_forge_rules: tool({
|
|
48596
|
-
description: "List forge automation rules.
|
|
48825
|
+
description: "List forge automation rules. Each rule includes xpImpact: true/false \u2014 use this to decide how to communicate the action to the user. Always call this before updating, deleting, or running a specific rule.",
|
|
48597
48826
|
inputSchema: exports_external.object({
|
|
48598
48827
|
enabledOnly: exports_external.boolean().optional().describe("When true, return only enabled rules."),
|
|
48599
48828
|
includeRecentRuns: exports_external.boolean().default(true).describe("When true, include recent execution history."),
|
|
@@ -48611,6 +48840,7 @@ function createGrindTools(ctx) {
|
|
|
48611
48840
|
enabled: rule.enabled,
|
|
48612
48841
|
triggerType: rule.triggerType,
|
|
48613
48842
|
actionType: rule.actionType,
|
|
48843
|
+
xpImpact: ["log-to-vault", "update-skill"].includes(rule.actionType),
|
|
48614
48844
|
triggerConfig: redactForgeValue(rule.triggerConfig),
|
|
48615
48845
|
actionConfig: redactForgeValue(rule.actionConfig),
|
|
48616
48846
|
updatedAt: rule.updatedAt
|
|
@@ -48685,13 +48915,13 @@ function createGrindTools(ctx) {
|
|
|
48685
48915
|
}
|
|
48686
48916
|
}),
|
|
48687
48917
|
create_forge_rule: tool({
|
|
48688
|
-
description: "Create a forge automation rule
|
|
48918
|
+
description: "Create a forge automation rule (queue-quest, log-to-vault, send-notification, run-script). send-notification and queue-quest have no XP impact \u2014 create autonomously. log-to-vault auto-awards XP \u2014 create it and mention that in your reply. run-script executes a shell script \u2014 always include the full script in your reply so the user can verify it.",
|
|
48689
48919
|
inputSchema: exports_external.object({
|
|
48690
48920
|
name: exports_external.string().min(2).max(128).describe("Human-readable rule name."),
|
|
48691
48921
|
triggerType: exports_external.enum(FORGE_TRIGGER_TYPES).describe("Trigger type (cron, event, signal, webhook, manual)."),
|
|
48692
48922
|
triggerConfig: exports_external.record(exports_external.string(), exports_external.unknown()).default({}).describe("Trigger configuration object."),
|
|
48693
48923
|
actionType: exports_external.enum(FORGE_ACTION_TYPES).describe("Action type (queue-quest, log-to-vault, send-notification)."),
|
|
48694
|
-
actionConfig: exports_external.record(exports_external.string(), exports_external.unknown()).default({}).describe("Action configuration object. " + "send-notification: required 'message' (static string) or 'script' (shell command whose stdout becomes the message), plus 'channel' (telegram/console/webhook/whatsapp) and channel credentials. " + "queue-quest: required 'questId'. " + "log-to-vault: required 'activityType', 'durationMinutes'."),
|
|
48924
|
+
actionConfig: exports_external.record(exports_external.string(), exports_external.unknown()).default({}).describe("Action configuration object. " + "send-notification: required 'message' (static string) or 'script' (shell command whose stdout becomes the message), plus 'channel' (telegram/console/webhook/whatsapp) and channel credentials. " + "queue-quest: required 'questId'. " + "log-to-vault: required 'activityType', 'durationMinutes'. " + "run-script: required 'script' (shell command), optional 'timeout' (ms, default 30000), optional 'workdir' (supports ~)."),
|
|
48695
48925
|
enabled: exports_external.boolean().default(true).describe("Whether the rule starts enabled.")
|
|
48696
48926
|
}),
|
|
48697
48927
|
execute: async ({ name: name21, triggerType, triggerConfig, actionType, actionConfig, enabled }) => {
|
|
@@ -48731,7 +48961,7 @@ function createGrindTools(ctx) {
|
|
|
48731
48961
|
}
|
|
48732
48962
|
}),
|
|
48733
48963
|
update_forge_rule: tool({
|
|
48734
|
-
description: "Update a forge rule by ID prefix or name.
|
|
48964
|
+
description: "Update a forge rule by ID prefix or name. Call list_forge_rules first to confirm the target. Act autonomously \u2014 no permission needed unless changing to a log-to-vault action, in which case mention the XP impact in your reply.",
|
|
48735
48965
|
inputSchema: exports_external.object({
|
|
48736
48966
|
ruleSearch: exports_external.string().min(1).describe("Rule ID prefix or rule name substring."),
|
|
48737
48967
|
name: exports_external.string().min(2).max(128).optional().describe("New rule name."),
|
|
@@ -48798,6 +49028,7 @@ function createGrindTools(ctx) {
|
|
|
48798
49028
|
nextTriggerConfig = normalized.triggerConfig;
|
|
48799
49029
|
nextActionConfig = normalized.actionConfig;
|
|
48800
49030
|
}
|
|
49031
|
+
const effectiveActionType = actionType ?? rule.actionType;
|
|
48801
49032
|
const updated = await updateForgeRule(ctx.db, ctx.userId, rule.id, {
|
|
48802
49033
|
...name21 !== undefined ? { name: name21 } : {},
|
|
48803
49034
|
...triggerType !== undefined ? { triggerType } : {},
|
|
@@ -48827,7 +49058,7 @@ function createGrindTools(ctx) {
|
|
|
48827
49058
|
}
|
|
48828
49059
|
}),
|
|
48829
49060
|
delete_forge_rule: tool({
|
|
48830
|
-
description: "Delete a forge rule by ID prefix or name.
|
|
49061
|
+
description: "Delete a forge rule by ID prefix or name. This is permanent \u2014 warn the user before calling this. Run history is also removed.",
|
|
48831
49062
|
inputSchema: exports_external.object({
|
|
48832
49063
|
ruleSearch: exports_external.string().min(1).describe("Rule ID prefix or rule name substring.")
|
|
48833
49064
|
}),
|
|
@@ -48836,6 +49067,9 @@ function createGrindTools(ctx) {
|
|
|
48836
49067
|
if (!rule) {
|
|
48837
49068
|
return { ok: false, error: `No forge rule matching "${ruleSearch}".` };
|
|
48838
49069
|
}
|
|
49070
|
+
const perm = await requirePermission(ctx, "delete_forge_rule", `Permanently delete forge rule "${rule.name}"?`);
|
|
49071
|
+
if (perm.denied)
|
|
49072
|
+
return { ok: false, error: "Deletion cancelled." };
|
|
48839
49073
|
const deleted = await deleteForgeRule(ctx.db, ctx.userId, rule.id);
|
|
48840
49074
|
if (!deleted) {
|
|
48841
49075
|
return { ok: false, error: "Failed to delete forge rule." };
|
|
@@ -48852,7 +49086,7 @@ function createGrindTools(ctx) {
|
|
|
48852
49086
|
}
|
|
48853
49087
|
}),
|
|
48854
49088
|
run_forge_rule: tool({
|
|
48855
|
-
description: "Run a forge rule immediately by ID prefix or name.
|
|
49089
|
+
description: "Run a forge rule immediately by ID prefix or name. Check xpImpact from list_forge_rules first: if false (notifications, reminders), run autonomously; if true (log-to-vault), run and mention the XP award in your reply.",
|
|
48856
49090
|
inputSchema: exports_external.object({
|
|
48857
49091
|
ruleSearch: exports_external.string().min(1).describe("Rule ID prefix or rule name substring."),
|
|
48858
49092
|
eventPayload: exports_external.record(exports_external.string(), exports_external.unknown()).optional().describe("Optional payload passed as event context for this run-now execution."),
|
|
@@ -48957,6 +49191,9 @@ function createGrindTools(ctx) {
|
|
|
48957
49191
|
baseXp: exports_external.number().int().positive().default(10).describe("Base XP before multipliers. 10=small, 25=medium, 50=large, 100=epic")
|
|
48958
49192
|
}),
|
|
48959
49193
|
execute: async ({ title, description, type, difficulty, skillTags, baseXp }) => {
|
|
49194
|
+
const trust2 = requireTrust(ctx, "create_quest");
|
|
49195
|
+
if (trust2.denied)
|
|
49196
|
+
return { error: trust2.error };
|
|
48960
49197
|
const active = await listQuestsByUser(ctx.db, ctx.userId, ["active"]);
|
|
48961
49198
|
if (active.length >= 5) {
|
|
48962
49199
|
return { error: "Max 5 active quests. Complete or abandon one first." };
|
|
@@ -48990,6 +49227,9 @@ function createGrindTools(ctx) {
|
|
|
48990
49227
|
durationMinutes: exports_external.number().int().positive().optional().describe("Duration in minutes if timer proof")
|
|
48991
49228
|
}),
|
|
48992
49229
|
execute: async ({ questSearch, proofType, durationMinutes }) => {
|
|
49230
|
+
const trust2 = requireTrust(ctx, "complete_quest");
|
|
49231
|
+
if (trust2.denied)
|
|
49232
|
+
return { error: trust2.error };
|
|
48993
49233
|
const quest2 = await findQuestByPrefix(ctx.db, ctx.userId, questSearch);
|
|
48994
49234
|
if (!quest2)
|
|
48995
49235
|
return { error: `No quest matching "${questSearch}"` };
|
|
@@ -49029,6 +49269,9 @@ function createGrindTools(ctx) {
|
|
|
49029
49269
|
questSearch: exports_external.string().describe("Quest ID prefix or title substring")
|
|
49030
49270
|
}),
|
|
49031
49271
|
execute: async ({ questSearch }) => {
|
|
49272
|
+
const trust2 = requireTrust(ctx, "abandon_quest");
|
|
49273
|
+
if (trust2.denied)
|
|
49274
|
+
return { error: trust2.error };
|
|
49032
49275
|
const quest2 = await findQuestByPrefix(ctx.db, ctx.userId, questSearch);
|
|
49033
49276
|
if (!quest2)
|
|
49034
49277
|
return { error: `No quest matching "${questSearch}"` };
|
|
@@ -49048,6 +49291,9 @@ function createGrindTools(ctx) {
|
|
|
49048
49291
|
questSearch: exports_external.string().describe("Quest ID prefix or title substring")
|
|
49049
49292
|
}),
|
|
49050
49293
|
execute: async ({ questSearch }) => {
|
|
49294
|
+
const trust2 = requireTrust(ctx, "start_timer");
|
|
49295
|
+
if (trust2.denied)
|
|
49296
|
+
return { error: trust2.error };
|
|
49051
49297
|
const existing = readTimer(ctx.timerPath);
|
|
49052
49298
|
if (existing) {
|
|
49053
49299
|
const elapsed = formatElapsed(existing.startedAt);
|
|
@@ -49075,6 +49321,9 @@ function createGrindTools(ctx) {
|
|
|
49075
49321
|
complete: exports_external.boolean().default(false).describe("Whether to also complete the quest with timer-duration proof")
|
|
49076
49322
|
}),
|
|
49077
49323
|
execute: async ({ complete }) => {
|
|
49324
|
+
const trust2 = requireTrust(ctx, "stop_timer");
|
|
49325
|
+
if (trust2.denied)
|
|
49326
|
+
return { error: trust2.error };
|
|
49078
49327
|
const timer = readTimer(ctx.timerPath);
|
|
49079
49328
|
if (!timer)
|
|
49080
49329
|
return { error: "No timer running" };
|
|
@@ -49812,6 +50061,182 @@ ${normalized}` : normalized;
|
|
|
49812
50061
|
return { pattern, matches, totalMatches: matches.length, truncated };
|
|
49813
50062
|
}
|
|
49814
50063
|
}),
|
|
50064
|
+
list_skills: tool({
|
|
50065
|
+
description: "List all skills with current XP, level, and category. Use this to see the full skill breakdown before making quest suggestions or updates.",
|
|
50066
|
+
inputSchema: exports_external.object({}),
|
|
50067
|
+
execute: async () => {
|
|
50068
|
+
const allSkills = await listSkillsByUser(ctx.db, ctx.userId);
|
|
50069
|
+
return allSkills.map((s) => ({
|
|
50070
|
+
id: s.id.slice(0, 8),
|
|
50071
|
+
name: s.name,
|
|
50072
|
+
category: s.category,
|
|
50073
|
+
xp: s.xp,
|
|
50074
|
+
level: s.level
|
|
50075
|
+
}));
|
|
50076
|
+
}
|
|
50077
|
+
}),
|
|
50078
|
+
list_quest_logs: tool({
|
|
50079
|
+
description: "List recent quest completion history. Returns completions with XP earned, duration, and proof type. Use this to see what the user has actually done recently.",
|
|
50080
|
+
inputSchema: exports_external.object({
|
|
50081
|
+
limit: exports_external.number().int().min(1).max(100).default(20).describe("Maximum number of logs to return"),
|
|
50082
|
+
sinceDaysAgo: exports_external.number().int().min(1).max(90).optional().describe("Only return logs from this many days ago")
|
|
50083
|
+
}),
|
|
50084
|
+
execute: async ({ limit, sinceDaysAgo }) => {
|
|
50085
|
+
const since = sinceDaysAgo ? Date.now() - sinceDaysAgo * 24 * 60 * 60 * 1000 : undefined;
|
|
50086
|
+
const logs = await listQuestLogs(ctx.db, ctx.userId, {
|
|
50087
|
+
limit,
|
|
50088
|
+
...since ? { since } : {}
|
|
50089
|
+
});
|
|
50090
|
+
const questIds = [...new Set(logs.map((l) => l.questId))];
|
|
50091
|
+
const questTitles = {};
|
|
50092
|
+
for (const qid of questIds) {
|
|
50093
|
+
const q3 = await getQuestById(ctx.db, qid);
|
|
50094
|
+
if (q3)
|
|
50095
|
+
questTitles[qid] = q3.title;
|
|
50096
|
+
}
|
|
50097
|
+
return logs.map((l) => ({
|
|
50098
|
+
questTitle: questTitles[l.questId] ?? l.questId.slice(0, 8),
|
|
50099
|
+
completedAt: new Date(l.completedAt).toLocaleDateString(),
|
|
50100
|
+
xpEarned: l.xpEarned,
|
|
50101
|
+
durationMinutes: l.durationMinutes ?? null,
|
|
50102
|
+
proofType: l.proofType,
|
|
50103
|
+
streakDay: l.streakDay
|
|
50104
|
+
}));
|
|
50105
|
+
}
|
|
50106
|
+
}),
|
|
50107
|
+
list_signals: tool({
|
|
50108
|
+
description: "List recently detected signals (git commits, file changes, process observations, webhook events). Use this to understand what the user has been doing passively.",
|
|
50109
|
+
inputSchema: exports_external.object({
|
|
50110
|
+
limit: exports_external.number().int().min(1).max(50).default(20).describe("Maximum number of signals to return"),
|
|
50111
|
+
source: exports_external.enum(["git", "file", "process", "webhook"]).optional().describe("Filter by signal source")
|
|
50112
|
+
}),
|
|
50113
|
+
execute: async ({ limit, source }) => {
|
|
50114
|
+
const sigs = await listSignals(ctx.db, ctx.userId, {
|
|
50115
|
+
limit,
|
|
50116
|
+
...source ? { source } : {}
|
|
50117
|
+
});
|
|
50118
|
+
return sigs.map((s) => ({
|
|
50119
|
+
source: s.source,
|
|
50120
|
+
type: s.type,
|
|
50121
|
+
confidence: s.confidence,
|
|
50122
|
+
detectedAt: new Date(s.detectedAt).toLocaleString(),
|
|
50123
|
+
payload: s.payload
|
|
50124
|
+
}));
|
|
50125
|
+
}
|
|
50126
|
+
}),
|
|
50127
|
+
update_quest: tool({
|
|
50128
|
+
description: "Update quest details: title, description, difficulty, type, baseXp, skillTags, or schedule. Does NOT change quest status \u2014 use abandon_quest or complete_quest for that.",
|
|
50129
|
+
inputSchema: exports_external.object({
|
|
50130
|
+
questSearch: exports_external.string().describe("Quest ID prefix or title substring"),
|
|
50131
|
+
title: exports_external.string().min(1).max(256).optional().describe("New quest title"),
|
|
50132
|
+
description: exports_external.string().max(2000).nullable().optional().describe("New description"),
|
|
50133
|
+
type: exports_external.enum(["daily", "weekly", "epic", "bounty", "chain", "ritual"]).optional().describe("New quest type"),
|
|
50134
|
+
difficulty: exports_external.enum(["easy", "medium", "hard", "epic"]).optional().describe("New difficulty"),
|
|
50135
|
+
skillTags: exports_external.array(exports_external.string()).optional().describe("Replace skill tags"),
|
|
50136
|
+
baseXp: exports_external.number().int().positive().optional().describe("New base XP (before multipliers)"),
|
|
50137
|
+
scheduleCron: exports_external.string().nullable().optional().describe("New cron schedule (null to remove)")
|
|
50138
|
+
}).refine((v) => v.title !== undefined || v.description !== undefined || v.type !== undefined || v.difficulty !== undefined || v.skillTags !== undefined || v.baseXp !== undefined || v.scheduleCron !== undefined, { message: "Provide at least one field to update." }),
|
|
50139
|
+
execute: async ({
|
|
50140
|
+
questSearch,
|
|
50141
|
+
title,
|
|
50142
|
+
description,
|
|
50143
|
+
type,
|
|
50144
|
+
difficulty,
|
|
50145
|
+
skillTags,
|
|
50146
|
+
baseXp,
|
|
50147
|
+
scheduleCron
|
|
50148
|
+
}) => {
|
|
50149
|
+
const trust2 = requireTrust(ctx, "update_quest");
|
|
50150
|
+
if (trust2.denied)
|
|
50151
|
+
return { error: trust2.error };
|
|
50152
|
+
const quest2 = await findQuestByPrefix(ctx.db, ctx.userId, questSearch);
|
|
50153
|
+
if (!quest2)
|
|
50154
|
+
return { error: `No quest matching "${questSearch}"` };
|
|
50155
|
+
const updated = await updateQuest(ctx.db, quest2.id, ctx.userId, {
|
|
50156
|
+
...title !== undefined ? { title } : {},
|
|
50157
|
+
...description !== undefined ? { description } : {},
|
|
50158
|
+
...type !== undefined ? { type } : {},
|
|
50159
|
+
...difficulty !== undefined ? { difficulty } : {},
|
|
50160
|
+
...skillTags !== undefined ? { skillTags } : {},
|
|
50161
|
+
...baseXp !== undefined ? { baseXp } : {},
|
|
50162
|
+
...scheduleCron !== undefined ? { scheduleCron } : {}
|
|
50163
|
+
});
|
|
50164
|
+
if (!updated)
|
|
50165
|
+
return { error: "Failed to update quest." };
|
|
50166
|
+
return {
|
|
50167
|
+
ok: true,
|
|
50168
|
+
id: updated.id.slice(0, 8),
|
|
50169
|
+
title: updated.title,
|
|
50170
|
+
type: updated.type,
|
|
50171
|
+
difficulty: updated.difficulty,
|
|
50172
|
+
skillTags: updated.skillTags,
|
|
50173
|
+
baseXp: updated.baseXp
|
|
50174
|
+
};
|
|
50175
|
+
}
|
|
50176
|
+
}),
|
|
50177
|
+
activate_quest: tool({
|
|
50178
|
+
description: "Move a quest from 'available' to 'active'. Use when the user is ready to start working on a quest that isn't active yet. Subject to the 5-quest active limit.",
|
|
50179
|
+
inputSchema: exports_external.object({
|
|
50180
|
+
questSearch: exports_external.string().describe("Quest ID prefix or title substring")
|
|
50181
|
+
}),
|
|
50182
|
+
execute: async ({ questSearch }) => {
|
|
50183
|
+
const trust2 = requireTrust(ctx, "activate_quest");
|
|
50184
|
+
if (trust2.denied)
|
|
50185
|
+
return { error: trust2.error };
|
|
50186
|
+
const quest2 = await findQuestByPrefix(ctx.db, ctx.userId, questSearch);
|
|
50187
|
+
if (!quest2)
|
|
50188
|
+
return { error: `No quest matching "${questSearch}"` };
|
|
50189
|
+
if (quest2.status === "active")
|
|
50190
|
+
return { error: "Quest is already active." };
|
|
50191
|
+
if (quest2.status === "completed")
|
|
50192
|
+
return { error: "Quest is completed \u2014 cannot reactivate." };
|
|
50193
|
+
if (quest2.status === "abandoned")
|
|
50194
|
+
return { error: "Quest is abandoned \u2014 cannot reactivate." };
|
|
50195
|
+
const active = await listQuestsByUser(ctx.db, ctx.userId, ["active"]);
|
|
50196
|
+
if (active.length >= 5) {
|
|
50197
|
+
return { error: "Max 5 active quests. Complete or abandon one first." };
|
|
50198
|
+
}
|
|
50199
|
+
await updateQuestStatus(ctx.db, quest2.id, ctx.userId, "active");
|
|
50200
|
+
return { ok: true, quest: quest2.title, status: "active" };
|
|
50201
|
+
}
|
|
50202
|
+
}),
|
|
50203
|
+
delete_insight: tool({
|
|
50204
|
+
description: "Delete a stored companion insight. Only AI-observed insights can be deleted. User-stated insights are permanent and cannot be removed by the companion.",
|
|
50205
|
+
inputSchema: exports_external.object({
|
|
50206
|
+
insightId: exports_external.string().min(1).describe("Insight ID (full UUID or short ID prefix)")
|
|
50207
|
+
}),
|
|
50208
|
+
execute: async ({ insightId }) => {
|
|
50209
|
+
const trust2 = requireTrust(ctx, "delete_insight");
|
|
50210
|
+
if (trust2.denied)
|
|
50211
|
+
return { error: trust2.error };
|
|
50212
|
+
const insights = await listCompanionInsights(ctx.db, ctx.userId, 200);
|
|
50213
|
+
const match = insights.find((i) => i.id === insightId || i.id.startsWith(insightId));
|
|
50214
|
+
if (!match)
|
|
50215
|
+
return { error: `No insight matching "${insightId}"` };
|
|
50216
|
+
if (match.source === "user-stated") {
|
|
50217
|
+
return {
|
|
50218
|
+
error: "Cannot delete user-stated insights. These are facts you provided \u2014 they can only be updated, not removed."
|
|
50219
|
+
};
|
|
50220
|
+
}
|
|
50221
|
+
const deleted = await deleteCompanionInsight(ctx.db, match.id, ctx.userId);
|
|
50222
|
+
if (!deleted)
|
|
50223
|
+
return { error: "Failed to delete insight." };
|
|
50224
|
+
return { ok: true, deleted: true, content: match.content };
|
|
50225
|
+
}
|
|
50226
|
+
}),
|
|
50227
|
+
update_companion_mode: tool({
|
|
50228
|
+
description: "Change the companion operating mode. 'suggest' = propose only, 'assist' = act on explicit requests, 'auto' = act proactively.",
|
|
50229
|
+
inputSchema: exports_external.object({
|
|
50230
|
+
mode: exports_external.enum(["off", "suggest", "assist", "auto"]).describe("New companion mode")
|
|
50231
|
+
}),
|
|
50232
|
+
execute: async ({ mode }) => {
|
|
50233
|
+
const trust2 = requireTrust(ctx, "update_companion_mode");
|
|
50234
|
+
if (trust2.denied)
|
|
50235
|
+
return { error: trust2.error };
|
|
50236
|
+
const updated = await updateCompanionMode(ctx.db, ctx.userId, mode);
|
|
50237
|
+
return { ok: true, mode: updated.mode };
|
|
50238
|
+
}
|
|
50239
|
+
}),
|
|
49815
50240
|
bash: tool({
|
|
49816
50241
|
description: "Execute a shell command. Returns stdout, stderr, and exit code. Both stdout and stderr are capped at 50KB.",
|
|
49817
50242
|
inputSchema: exports_external.object({
|
|
@@ -49896,7 +50321,7 @@ ${normalized}` : normalized;
|
|
|
49896
50321
|
})
|
|
49897
50322
|
};
|
|
49898
50323
|
}
|
|
49899
|
-
var MAX_FETCH_SIZE, MAX_READ_SIZE, MAX_BASH_OUTPUT, DEFAULT_READ_LIMIT = 2000, MAX_LINE_LENGTH = 2000, FORGE_TRIGGER_TYPES, FORGE_ACTION_TYPES, FORGE_NOTIFICATION_CHANNELS, FORGE_WEBHOOK_CHANNEL_TAGS, SIMPLE_CRON_FIELD_REGEX, FORGE_SENSITIVE_KEYS;
|
|
50324
|
+
var TRUST_LEVEL_NAMES, TOOL_TRUST_REQUIREMENTS, MAX_FETCH_SIZE, MAX_READ_SIZE, MAX_BASH_OUTPUT, DEFAULT_READ_LIMIT = 2000, MAX_LINE_LENGTH = 2000, FORGE_TRIGGER_TYPES, FORGE_ACTION_TYPES, FORGE_NOTIFICATION_CHANNELS, FORGE_WEBHOOK_CHANNEL_TAGS, SIMPLE_CRON_FIELD_REGEX, FORGE_SENSITIVE_KEYS;
|
|
49900
50325
|
var init_tools = __esm(() => {
|
|
49901
50326
|
init_dist5();
|
|
49902
50327
|
init_drizzle_orm();
|
|
@@ -49911,11 +50336,28 @@ var init_tools = __esm(() => {
|
|
|
49911
50336
|
init_schema2();
|
|
49912
50337
|
init_repositories();
|
|
49913
50338
|
init_constants();
|
|
50339
|
+
TRUST_LEVEL_NAMES = ["Watcher", "Advisor", "Scribe", "Agent", "Sovereign"];
|
|
50340
|
+
TOOL_TRUST_REQUIREMENTS = {
|
|
50341
|
+
complete_quest: 2,
|
|
50342
|
+
abandon_quest: 2,
|
|
50343
|
+
activate_quest: 2,
|
|
50344
|
+
start_timer: 2,
|
|
50345
|
+
stop_timer: 2,
|
|
50346
|
+
update_companion_mode: 2,
|
|
50347
|
+
create_quest: 3,
|
|
50348
|
+
update_quest: 3,
|
|
50349
|
+
delete_insight: 4
|
|
50350
|
+
};
|
|
49914
50351
|
MAX_FETCH_SIZE = 50 * 1024;
|
|
49915
50352
|
MAX_READ_SIZE = 50 * 1024;
|
|
49916
50353
|
MAX_BASH_OUTPUT = 50 * 1024;
|
|
49917
50354
|
FORGE_TRIGGER_TYPES = ["cron", "event", "signal", "webhook", "manual"];
|
|
49918
|
-
FORGE_ACTION_TYPES = [
|
|
50355
|
+
FORGE_ACTION_TYPES = [
|
|
50356
|
+
"queue-quest",
|
|
50357
|
+
"log-to-vault",
|
|
50358
|
+
"send-notification",
|
|
50359
|
+
"run-script"
|
|
50360
|
+
];
|
|
49919
50361
|
FORGE_NOTIFICATION_CHANNELS = ["console", "telegram", "webhook", "whatsapp"];
|
|
49920
50362
|
FORGE_WEBHOOK_CHANNEL_TAGS = ["webhook", "telegram", "discord", "whatsapp"];
|
|
49921
50363
|
SIMPLE_CRON_FIELD_REGEX = /^[\d*/,\-]+$/;
|
|
@@ -83124,9 +83566,10 @@ function ChatScreen(props) {
|
|
|
83124
83566
|
const [pasteBuffers, setPasteBuffers] = import_react16.useState([]);
|
|
83125
83567
|
const pasteCountRef = import_react16.useRef(0);
|
|
83126
83568
|
const [pastePreviewOpen, setPastePreviewOpen] = import_react16.useState(false);
|
|
83127
|
-
const
|
|
83569
|
+
const [hasPromptText, setHasPromptText] = import_react16.useState(false);
|
|
83570
|
+
const historyRef = import_react16.useRef(props.initialPromptHistory ?? []);
|
|
83128
83571
|
const historyDraftRef = import_react16.useRef("");
|
|
83129
|
-
const [
|
|
83572
|
+
const [historyNavPos, setHistoryNavPos] = import_react16.useState(null);
|
|
83130
83573
|
const suggestions = import_react16.useMemo(() => matchCommands(cmdBuffer), [cmdBuffer]);
|
|
83131
83574
|
const ghostText = import_react16.useMemo(() => getGhostCompletion(cmdBuffer), [cmdBuffer]);
|
|
83132
83575
|
const effectiveMode = permissionPrompt ? "permission" : sessionList && sessionList.length > 0 ? "sessions" : commandMode;
|
|
@@ -83170,18 +83613,59 @@ function ChatScreen(props) {
|
|
|
83170
83613
|
setPickerSearch("");
|
|
83171
83614
|
setPickerSelectedIndex(0);
|
|
83172
83615
|
}, []);
|
|
83616
|
+
const clearPromptAll = import_react16.useCallback(() => {
|
|
83617
|
+
textareaRef.current?.clear();
|
|
83618
|
+
textareaRef.current?.extmarks?.clear();
|
|
83619
|
+
for (const att of attachments)
|
|
83620
|
+
onRemoveAttachment(att.id);
|
|
83621
|
+
setPasteBuffers([]);
|
|
83622
|
+
setPastePreviewOpen(false);
|
|
83623
|
+
setHasPromptText(false);
|
|
83624
|
+
historyDraftRef.current = "";
|
|
83625
|
+
setHistoryNavPos(null);
|
|
83626
|
+
}, [attachments, onRemoveAttachment]);
|
|
83627
|
+
function navigateHistory(direction) {
|
|
83628
|
+
const textarea = textareaRef.current;
|
|
83629
|
+
if (!textarea)
|
|
83630
|
+
return;
|
|
83631
|
+
const history = historyRef.current;
|
|
83632
|
+
if (direction === "up") {
|
|
83633
|
+
if (history.length === 0)
|
|
83634
|
+
return;
|
|
83635
|
+
const curIdx = historyNavPos !== null ? historyNavPos.i - 1 : -1;
|
|
83636
|
+
const nextIdx = curIdx === -1 ? history.length - 1 : Math.max(0, curIdx - 1);
|
|
83637
|
+
if (nextIdx === curIdx)
|
|
83638
|
+
return;
|
|
83639
|
+
if (curIdx === -1)
|
|
83640
|
+
historyDraftRef.current = textarea.plainText;
|
|
83641
|
+
setHistoryNavPos({ i: nextIdx + 1, n: history.length });
|
|
83642
|
+
textarea.setText(history[nextIdx]);
|
|
83643
|
+
textarea.cursorOffset = 0;
|
|
83644
|
+
setHasPromptText(true);
|
|
83645
|
+
} else {
|
|
83646
|
+
if (historyNavPos === null)
|
|
83647
|
+
return;
|
|
83648
|
+
const nextIdx = historyNavPos.i;
|
|
83649
|
+
if (nextIdx >= history.length) {
|
|
83650
|
+
setHistoryNavPos(null);
|
|
83651
|
+
const draft = historyDraftRef.current;
|
|
83652
|
+
textarea.setText(draft);
|
|
83653
|
+
textarea.cursorOffset = draft.length;
|
|
83654
|
+
setHasPromptText(draft.length > 0);
|
|
83655
|
+
} else {
|
|
83656
|
+
setHistoryNavPos({ i: nextIdx + 1, n: history.length });
|
|
83657
|
+
textarea.setText(history[nextIdx]);
|
|
83658
|
+
textarea.cursorOffset = history[nextIdx].length;
|
|
83659
|
+
setHasPromptText(true);
|
|
83660
|
+
}
|
|
83661
|
+
}
|
|
83662
|
+
}
|
|
83173
83663
|
const imageTypeIdRef = import_react16.useRef(null);
|
|
83174
83664
|
const handleContentChange = useEffectEvent2(() => {
|
|
83175
83665
|
if (commandMode !== "idle")
|
|
83176
83666
|
return;
|
|
83177
83667
|
const value = textareaRef.current?.plainText ?? "";
|
|
83178
|
-
|
|
83179
|
-
const expected = promptHistoryRef.current[historyIndex];
|
|
83180
|
-
if (value !== expected) {
|
|
83181
|
-
setHistoryIndex(-1);
|
|
83182
|
-
}
|
|
83183
|
-
return;
|
|
83184
|
-
}
|
|
83668
|
+
setHasPromptText(value.length > 0);
|
|
83185
83669
|
if (value === "/") {
|
|
83186
83670
|
textareaRef.current?.clear();
|
|
83187
83671
|
setCommandMode("suggesting");
|
|
@@ -83237,18 +83721,22 @@ function ChatScreen(props) {
|
|
|
83237
83721
|
if (!text4 && !hasImages)
|
|
83238
83722
|
return;
|
|
83239
83723
|
if (text4) {
|
|
83240
|
-
const h =
|
|
83724
|
+
const h = historyRef.current;
|
|
83241
83725
|
if (h[h.length - 1] !== text4) {
|
|
83242
|
-
|
|
83726
|
+
historyRef.current = [...h, text4].slice(-PROMPT_HISTORY_MAX2);
|
|
83243
83727
|
}
|
|
83244
83728
|
}
|
|
83245
|
-
setHistoryIndex(-1);
|
|
83246
83729
|
historyDraftRef.current = "";
|
|
83730
|
+
setHistoryNavPos(null);
|
|
83247
83731
|
onSend(text4);
|
|
83248
83732
|
textareaRef.current?.extmarks?.clear();
|
|
83249
83733
|
textareaRef.current?.clear();
|
|
83250
83734
|
}, [onSend, pasteBuffers]);
|
|
83251
83735
|
const handlePaste = import_react16.useCallback((event) => {
|
|
83736
|
+
if (historyNavPos !== null) {
|
|
83737
|
+
historyDraftRef.current = "";
|
|
83738
|
+
setHistoryNavPos(null);
|
|
83739
|
+
}
|
|
83252
83740
|
const normalized = event.text.replace(/\r\n/g, `
|
|
83253
83741
|
`).replace(/\r/g, `
|
|
83254
83742
|
`);
|
|
@@ -83303,6 +83791,13 @@ function ChatScreen(props) {
|
|
|
83303
83791
|
}, [onSessionSelect]);
|
|
83304
83792
|
useKeyboard((key) => {
|
|
83305
83793
|
if (key.ctrl && key.name === "c") {
|
|
83794
|
+
if (effectiveMode === "idle") {
|
|
83795
|
+
const text4 = textareaRef.current?.plainText ?? "";
|
|
83796
|
+
if (text4 || attachments.length > 0 || pasteBuffers.length > 0) {
|
|
83797
|
+
clearPromptAll();
|
|
83798
|
+
return;
|
|
83799
|
+
}
|
|
83800
|
+
}
|
|
83306
83801
|
onExit();
|
|
83307
83802
|
return;
|
|
83308
83803
|
}
|
|
@@ -83430,51 +83925,6 @@ function ChatScreen(props) {
|
|
|
83430
83925
|
key.preventDefault();
|
|
83431
83926
|
return;
|
|
83432
83927
|
}
|
|
83433
|
-
if (mode === "idle" && key.name === "up") {
|
|
83434
|
-
const plain = textareaRef.current?.plainText ?? "";
|
|
83435
|
-
const cursor = textareaRef.current?.cursorOffset ?? 0;
|
|
83436
|
-
const onFirstLine = !plain.slice(0, cursor).includes(`
|
|
83437
|
-
`);
|
|
83438
|
-
const history = promptHistoryRef.current;
|
|
83439
|
-
if (onFirstLine && history.length > 0) {
|
|
83440
|
-
key.preventDefault();
|
|
83441
|
-
const curIdx = historyIndex;
|
|
83442
|
-
const nextIdx = curIdx === -1 ? history.length - 1 : Math.max(0, curIdx - 1);
|
|
83443
|
-
if (nextIdx !== curIdx) {
|
|
83444
|
-
if (curIdx === -1)
|
|
83445
|
-
historyDraftRef.current = plain;
|
|
83446
|
-
const entry = history[nextIdx];
|
|
83447
|
-
if (entry !== undefined) {
|
|
83448
|
-
setHistoryIndex(nextIdx);
|
|
83449
|
-
textareaRef.current?.clear();
|
|
83450
|
-
textareaRef.current?.extmarks?.clear();
|
|
83451
|
-
textareaRef.current?.insertText(entry);
|
|
83452
|
-
}
|
|
83453
|
-
}
|
|
83454
|
-
return;
|
|
83455
|
-
}
|
|
83456
|
-
}
|
|
83457
|
-
if (mode === "idle" && key.name === "down" && historyIndex !== -1) {
|
|
83458
|
-
key.preventDefault();
|
|
83459
|
-
const history = promptHistoryRef.current;
|
|
83460
|
-
const nextIdx = historyIndex + 1;
|
|
83461
|
-
if (nextIdx >= history.length) {
|
|
83462
|
-
setHistoryIndex(-1);
|
|
83463
|
-
textareaRef.current?.clear();
|
|
83464
|
-
textareaRef.current?.extmarks?.clear();
|
|
83465
|
-
if (historyDraftRef.current)
|
|
83466
|
-
textareaRef.current?.insertText(historyDraftRef.current);
|
|
83467
|
-
} else {
|
|
83468
|
-
const entry = history[nextIdx];
|
|
83469
|
-
if (entry !== undefined) {
|
|
83470
|
-
setHistoryIndex(nextIdx);
|
|
83471
|
-
textareaRef.current?.clear();
|
|
83472
|
-
textareaRef.current?.extmarks?.clear();
|
|
83473
|
-
textareaRef.current?.insertText(entry);
|
|
83474
|
-
}
|
|
83475
|
-
}
|
|
83476
|
-
return;
|
|
83477
|
-
}
|
|
83478
83928
|
if (mode === "idle" && attachments.length > 0 && (key.meta && key.name === "backspace" || key.ctrl && key.name === "backspace" || key.ctrl && key.name === "w" || key.meta && key.name === "delete")) {
|
|
83479
83929
|
const textarea = textareaRef.current;
|
|
83480
83930
|
const extmarks = textarea?.extmarks;
|
|
@@ -83518,11 +83968,36 @@ function ChatScreen(props) {
|
|
|
83518
83968
|
setPastePreviewOpen((open) => !open);
|
|
83519
83969
|
return;
|
|
83520
83970
|
}
|
|
83971
|
+
if (mode === "idle" && historyNavPos !== null) {
|
|
83972
|
+
const isChar = !key.ctrl && !key.meta && key.sequence && key.sequence.length === 1 && key.sequence.charCodeAt(0) >= 32;
|
|
83973
|
+
if (isChar || key.name === "backspace" || key.name === "delete") {
|
|
83974
|
+
historyDraftRef.current = "";
|
|
83975
|
+
setHistoryNavPos(null);
|
|
83976
|
+
}
|
|
83977
|
+
}
|
|
83978
|
+
if (mode === "idle" && key.name === "up" && !key.ctrl && !key.meta) {
|
|
83979
|
+
const offset = textareaRef.current?.cursorOffset ?? 0;
|
|
83980
|
+
if (offset === 0 && historyRef.current.length > 0) {
|
|
83981
|
+
key.preventDefault();
|
|
83982
|
+
navigateHistory("up");
|
|
83983
|
+
return;
|
|
83984
|
+
}
|
|
83985
|
+
}
|
|
83986
|
+
if (mode === "idle" && key.name === "down" && !key.ctrl && !key.meta && historyNavPos !== null) {
|
|
83987
|
+
key.preventDefault();
|
|
83988
|
+
navigateHistory("down");
|
|
83989
|
+
return;
|
|
83990
|
+
}
|
|
83521
83991
|
if (key.name === "escape") {
|
|
83522
83992
|
if (isStreaming) {
|
|
83523
83993
|
onAbort();
|
|
83524
83994
|
} else {
|
|
83525
|
-
|
|
83995
|
+
const text4 = textareaRef.current?.plainText ?? "";
|
|
83996
|
+
if (text4 || attachments.length > 0 || pasteBuffers.length > 0) {
|
|
83997
|
+
clearPromptAll();
|
|
83998
|
+
} else {
|
|
83999
|
+
onExit();
|
|
84000
|
+
}
|
|
83526
84001
|
}
|
|
83527
84002
|
}
|
|
83528
84003
|
});
|
|
@@ -83846,9 +84321,13 @@ function ChatScreen(props) {
|
|
|
83846
84321
|
focused: textareaFocused,
|
|
83847
84322
|
minHeight: 1,
|
|
83848
84323
|
maxHeight: 6,
|
|
84324
|
+
flexShrink: 0,
|
|
83849
84325
|
keyBindings: [
|
|
83850
84326
|
{ name: "return", action: "submit" },
|
|
83851
|
-
{ name: "return", meta: true, action: "newline" }
|
|
84327
|
+
{ name: "return", meta: true, action: "newline" },
|
|
84328
|
+
{ name: "return", ctrl: true, action: "newline" },
|
|
84329
|
+
{ name: "return", shift: true, action: "newline" },
|
|
84330
|
+
{ name: "j", ctrl: true, action: "newline" }
|
|
83852
84331
|
]
|
|
83853
84332
|
}, undefined, false, undefined, this)
|
|
83854
84333
|
}, undefined, false, undefined, this)
|
|
@@ -84053,7 +84532,43 @@ function ChatScreen(props) {
|
|
|
84053
84532
|
}, undefined, true, undefined, this)
|
|
84054
84533
|
]
|
|
84055
84534
|
}, undefined, true, undefined, this),
|
|
84056
|
-
effectiveMode === "idle" && !isStreaming && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(import_jsx_dev_runtime2.Fragment, {
|
|
84535
|
+
effectiveMode === "idle" && !isStreaming && historyNavPos !== null && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(import_jsx_dev_runtime2.Fragment, {
|
|
84536
|
+
children: [
|
|
84537
|
+
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
84538
|
+
fg: colors.muted,
|
|
84539
|
+
children: [
|
|
84540
|
+
"\u2191\u2193",
|
|
84541
|
+
" history ",
|
|
84542
|
+
historyNavPos.i,
|
|
84543
|
+
"/",
|
|
84544
|
+
historyNavPos.n
|
|
84545
|
+
]
|
|
84546
|
+
}, undefined, true, undefined, this),
|
|
84547
|
+
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
84548
|
+
fg: colors.textDim,
|
|
84549
|
+
children: "\xB7"
|
|
84550
|
+
}, undefined, false, undefined, this),
|
|
84551
|
+
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
84552
|
+
fg: colors.textDim,
|
|
84553
|
+
children: [
|
|
84554
|
+
"\u2193",
|
|
84555
|
+
" newer"
|
|
84556
|
+
]
|
|
84557
|
+
}, undefined, true, undefined, this),
|
|
84558
|
+
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
84559
|
+
fg: colors.textDim,
|
|
84560
|
+
children: [
|
|
84561
|
+
"[",
|
|
84562
|
+
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
|
|
84563
|
+
fg: colors.accent,
|
|
84564
|
+
children: "Esc"
|
|
84565
|
+
}, undefined, false, undefined, this),
|
|
84566
|
+
"] cancel"
|
|
84567
|
+
]
|
|
84568
|
+
}, undefined, true, undefined, this)
|
|
84569
|
+
]
|
|
84570
|
+
}, undefined, true, undefined, this),
|
|
84571
|
+
effectiveMode === "idle" && !isStreaming && historyNavPos === null && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(import_jsx_dev_runtime2.Fragment, {
|
|
84057
84572
|
children: [
|
|
84058
84573
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
84059
84574
|
fg: colors.muted,
|
|
@@ -84105,43 +84620,44 @@ function ChatScreen(props) {
|
|
|
84105
84620
|
}, undefined, true, undefined, this),
|
|
84106
84621
|
!zenMode && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV(import_jsx_dev_runtime2.Fragment, {
|
|
84107
84622
|
children: [
|
|
84108
|
-
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
84623
|
+
historyRef.current.length > 0 && /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
84109
84624
|
fg: colors.textDim,
|
|
84110
84625
|
children: [
|
|
84111
84626
|
"[",
|
|
84112
84627
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
|
|
84113
84628
|
fg: colors.accent,
|
|
84114
|
-
children: "
|
|
84629
|
+
children: "\u2191"
|
|
84115
84630
|
}, undefined, false, undefined, this),
|
|
84116
|
-
"]
|
|
84631
|
+
"] history"
|
|
84117
84632
|
]
|
|
84118
84633
|
}, undefined, true, undefined, this),
|
|
84119
|
-
|
|
84120
|
-
fg: colors.
|
|
84634
|
+
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
84635
|
+
fg: colors.textDim,
|
|
84121
84636
|
children: [
|
|
84122
|
-
"
|
|
84123
|
-
historyIndex + 1,
|
|
84124
|
-
"/",
|
|
84125
|
-
promptHistoryRef.current.length,
|
|
84126
|
-
"]",
|
|
84127
|
-
" ",
|
|
84637
|
+
"[",
|
|
84128
84638
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
|
|
84129
|
-
fg: colors.
|
|
84130
|
-
children: "
|
|
84131
|
-
}, undefined, false, undefined, this)
|
|
84639
|
+
fg: colors.accent,
|
|
84640
|
+
children: "/"
|
|
84641
|
+
}, undefined, false, undefined, this),
|
|
84642
|
+
"] cmds"
|
|
84132
84643
|
]
|
|
84133
|
-
}, undefined, true, undefined, this)
|
|
84644
|
+
}, undefined, true, undefined, this),
|
|
84645
|
+
hasPromptText || attachments.length > 0 || pasteBuffers.length > 0 ? /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
84134
84646
|
fg: colors.textDim,
|
|
84135
84647
|
children: [
|
|
84136
84648
|
"[",
|
|
84137
84649
|
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
|
|
84138
84650
|
fg: colors.accent,
|
|
84139
|
-
children: "
|
|
84651
|
+
children: "Esc"
|
|
84140
84652
|
}, undefined, false, undefined, this),
|
|
84141
|
-
"
|
|
84653
|
+
"/",
|
|
84654
|
+
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("span", {
|
|
84655
|
+
fg: colors.accent,
|
|
84656
|
+
children: "^C"
|
|
84657
|
+
}, undefined, false, undefined, this),
|
|
84658
|
+
"] clear"
|
|
84142
84659
|
]
|
|
84143
|
-
}, undefined, true, undefined, this),
|
|
84144
|
-
/* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
84660
|
+
}, undefined, true, undefined, this) : /* @__PURE__ */ import_jsx_dev_runtime2.jsxDEV("text", {
|
|
84145
84661
|
fg: colors.textDim,
|
|
84146
84662
|
children: [
|
|
84147
84663
|
"[",
|
|
@@ -84149,7 +84665,7 @@ function ChatScreen(props) {
|
|
|
84149
84665
|
fg: colors.accent,
|
|
84150
84666
|
children: "Esc"
|
|
84151
84667
|
}, undefined, false, undefined, this),
|
|
84152
|
-
"]
|
|
84668
|
+
"] exit"
|
|
84153
84669
|
]
|
|
84154
84670
|
}, undefined, true, undefined, this)
|
|
84155
84671
|
]
|
|
@@ -84193,7 +84709,7 @@ function ChatScreen(props) {
|
|
|
84193
84709
|
]
|
|
84194
84710
|
}, undefined, true, undefined, this);
|
|
84195
84711
|
}
|
|
84196
|
-
var import_react16, IMAGE_EXTS2, IMAGE_LABEL_RE, IMAGE_LABEL_SPLIT, COLLAPSED_LINES = 8, TOOL_ICONS, BLOCK_TOOLS, CMD_NAME_COL = 14
|
|
84712
|
+
var import_react16, IMAGE_EXTS2, IMAGE_LABEL_RE, IMAGE_LABEL_SPLIT, COLLAPSED_LINES = 8, PROMPT_HISTORY_MAX2 = 100, TOOL_ICONS, BLOCK_TOOLS, CMD_NAME_COL = 14;
|
|
84197
84713
|
var init_ChatScreen = __esm(async () => {
|
|
84198
84714
|
init_use_effect_event();
|
|
84199
84715
|
init_clipboard();
|
|
@@ -85290,7 +85806,8 @@ function renderChat(root, params) {
|
|
|
85290
85806
|
db: params.db,
|
|
85291
85807
|
userId: params.userId,
|
|
85292
85808
|
timerPath: params.timerPath,
|
|
85293
|
-
config: params.config
|
|
85809
|
+
config: params.config,
|
|
85810
|
+
trustLevel: params.companion?.trustLevel ?? 0
|
|
85294
85811
|
};
|
|
85295
85812
|
const promptCtx = {
|
|
85296
85813
|
user: params.user,
|
|
@@ -86865,6 +87382,85 @@ async function companionMemoryEditCommand(ctx, idPrefix) {
|
|
|
86865
87382
|
});
|
|
86866
87383
|
R2.success(`Updated insight ${updated.id.slice(0, 8)}.`);
|
|
86867
87384
|
}
|
|
87385
|
+
var TRUST_LEVELS = [
|
|
87386
|
+
{
|
|
87387
|
+
level: 0,
|
|
87388
|
+
name: "Watcher",
|
|
87389
|
+
minScore: 0,
|
|
87390
|
+
description: "Read-only. Manages forge rules by risk (notifications/reminders freely)."
|
|
87391
|
+
},
|
|
87392
|
+
{
|
|
87393
|
+
level: 1,
|
|
87394
|
+
name: "Advisor",
|
|
87395
|
+
minScore: 10,
|
|
87396
|
+
description: "Can suggest quests."
|
|
87397
|
+
},
|
|
87398
|
+
{
|
|
87399
|
+
level: 2,
|
|
87400
|
+
name: "Scribe",
|
|
87401
|
+
minScore: 25,
|
|
87402
|
+
description: "Can complete/abandon quests, start/stop timers, update companion mode."
|
|
87403
|
+
},
|
|
87404
|
+
{
|
|
87405
|
+
level: 3,
|
|
87406
|
+
name: "Agent",
|
|
87407
|
+
minScore: 50,
|
|
87408
|
+
description: "Can create and update quests."
|
|
87409
|
+
},
|
|
87410
|
+
{
|
|
87411
|
+
level: 4,
|
|
87412
|
+
name: "Sovereign",
|
|
87413
|
+
minScore: 100,
|
|
87414
|
+
description: "Can delete insights."
|
|
87415
|
+
}
|
|
87416
|
+
];
|
|
87417
|
+
async function companionTrustCommand(ctx, levelArg) {
|
|
87418
|
+
const companion4 = await requireCompanion(ctx);
|
|
87419
|
+
if (levelArg === undefined) {
|
|
87420
|
+
const currentName = TRUST_LEVELS.find((t) => t.level === companion4.trustLevel)?.name ?? `Lv.${companion4.trustLevel}`;
|
|
87421
|
+
Ve([
|
|
87422
|
+
`Current: ${currentName} (level ${companion4.trustLevel}, score ${companion4.trustScore})`,
|
|
87423
|
+
"",
|
|
87424
|
+
"Available levels:",
|
|
87425
|
+
...TRUST_LEVELS.map((t) => {
|
|
87426
|
+
const marker21 = t.level === companion4.trustLevel ? "\u25B6" : " ";
|
|
87427
|
+
return ` ${marker21} ${t.level} ${t.name.padEnd(10)} ${t.description}`;
|
|
87428
|
+
}),
|
|
87429
|
+
"",
|
|
87430
|
+
"Grant trust: grindxp companion trust <0-4>"
|
|
87431
|
+
].join(`
|
|
87432
|
+
`), "Companion Trust");
|
|
87433
|
+
return;
|
|
87434
|
+
}
|
|
87435
|
+
const parsed = Number.parseInt(levelArg, 10);
|
|
87436
|
+
if (!Number.isInteger(parsed) || parsed < 0 || parsed > 4) {
|
|
87437
|
+
R2.error("Trust level must be 0\u20134.");
|
|
87438
|
+
return;
|
|
87439
|
+
}
|
|
87440
|
+
const target = TRUST_LEVELS[parsed];
|
|
87441
|
+
if (!target) {
|
|
87442
|
+
R2.error("Invalid trust level.");
|
|
87443
|
+
return;
|
|
87444
|
+
}
|
|
87445
|
+
if (parsed === companion4.trustLevel) {
|
|
87446
|
+
R2.info(`Companion is already at ${target.name} (level ${parsed}).`);
|
|
87447
|
+
return;
|
|
87448
|
+
}
|
|
87449
|
+
const action = parsed > companion4.trustLevel ? "Grant" : "Revoke to";
|
|
87450
|
+
const confirm = await Re({
|
|
87451
|
+
message: `${action} trust level ${parsed} (${target.name})? ${target.description}`
|
|
87452
|
+
});
|
|
87453
|
+
if (Ct(confirm) || !confirm) {
|
|
87454
|
+
Ne("Cancelled.");
|
|
87455
|
+
return;
|
|
87456
|
+
}
|
|
87457
|
+
await upsertCompanion(ctx.db, {
|
|
87458
|
+
...companion4,
|
|
87459
|
+
trustLevel: parsed,
|
|
87460
|
+
trustScore: Math.max(companion4.trustScore, target.minScore)
|
|
87461
|
+
});
|
|
87462
|
+
R2.success(`Trust updated to ${target.name} (level ${parsed}).`);
|
|
87463
|
+
}
|
|
86868
87464
|
async function companionMemoryDeleteCommand(ctx, idPrefix) {
|
|
86869
87465
|
await requireCompanion(ctx);
|
|
86870
87466
|
const insightId = await chooseInsightId(ctx, idPrefix);
|
|
@@ -87423,6 +88019,7 @@ async function handleTelegramEvent(options) {
|
|
|
87423
88019
|
userId: options.userId,
|
|
87424
88020
|
timerPath: getTimerPath(),
|
|
87425
88021
|
config: options.config,
|
|
88022
|
+
trustLevel: companion4?.trustLevel ?? 0,
|
|
87426
88023
|
requestPermission: async (toolName, detail) => {
|
|
87427
88024
|
if (options.alwaysAllowedTools.has(toolName)) {
|
|
87428
88025
|
return "once";
|
|
@@ -87587,17 +88184,12 @@ function parsePermissionCallbackData(data) {
|
|
|
87587
88184
|
}
|
|
87588
88185
|
function ensureTrustedTelegramChat(options, chatId) {
|
|
87589
88186
|
if (!options.trustedChatId) {
|
|
87590
|
-
|
|
88187
|
+
const onDisk = readGrindConfig() ?? options.config;
|
|
88188
|
+
const gatewayBase = onDisk.gateway ?? options.config.gateway;
|
|
88189
|
+
if (!gatewayBase) {
|
|
87591
88190
|
return false;
|
|
87592
88191
|
}
|
|
87593
|
-
|
|
87594
|
-
...options.config,
|
|
87595
|
-
gateway: {
|
|
87596
|
-
...options.config.gateway,
|
|
87597
|
-
telegramDefaultChatId: chatId
|
|
87598
|
-
}
|
|
87599
|
-
};
|
|
87600
|
-
writeGrindConfig(nextConfig);
|
|
88192
|
+
writeGrindConfig({ ...onDisk, gateway: { ...gatewayBase, telegramDefaultChatId: chatId } });
|
|
87601
88193
|
options.setTrustedChatId(chatId);
|
|
87602
88194
|
options.onWarn?.(`Auto-set telegramDefaultChatId to ${chatId}.`);
|
|
87603
88195
|
return true;
|
|
@@ -88050,7 +88642,7 @@ import { join as join6 } from "path";
|
|
|
88050
88642
|
// src/gateway/autostart.ts
|
|
88051
88643
|
await init_src();
|
|
88052
88644
|
import { existsSync as existsSync6, mkdirSync as mkdirSync3, unlinkSync as unlinkSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
88053
|
-
import { homedir as
|
|
88645
|
+
import { homedir as homedir3 } from "os";
|
|
88054
88646
|
import { join as join5, resolve as resolve4 } from "path";
|
|
88055
88647
|
var SYSTEMD_UNIT_NAME = "grind-gateway.service";
|
|
88056
88648
|
var LAUNCHD_LABEL = "com.grind.gateway";
|
|
@@ -88237,7 +88829,7 @@ async function runSystemdCommand(args, options) {
|
|
|
88237
88829
|
return runCommand(["systemctl", "--user", ...args], options);
|
|
88238
88830
|
}
|
|
88239
88831
|
function getSystemdUnitDir() {
|
|
88240
|
-
return join5(
|
|
88832
|
+
return join5(homedir3(), ".config", "systemd", "user");
|
|
88241
88833
|
}
|
|
88242
88834
|
function getSystemdUnitPath() {
|
|
88243
88835
|
return join5(getSystemdUnitDir(), SYSTEMD_UNIT_NAME);
|
|
@@ -88361,7 +88953,7 @@ function buildLaunchdPlist(launchSpec) {
|
|
|
88361
88953
|
`);
|
|
88362
88954
|
}
|
|
88363
88955
|
function getLaunchdDir() {
|
|
88364
|
-
return join5(
|
|
88956
|
+
return join5(homedir3(), "Library", "LaunchAgents");
|
|
88365
88957
|
}
|
|
88366
88958
|
function getLaunchdPlistPath() {
|
|
88367
88959
|
return join5(getLaunchdDir(), `${LAUNCHD_LABEL}.plist`);
|
|
@@ -90079,9 +90671,14 @@ async function forgeCreateCommand(ctx) {
|
|
|
90079
90671
|
const actionType = await Je({
|
|
90080
90672
|
message: "Action type:",
|
|
90081
90673
|
options: [
|
|
90082
|
-
{
|
|
90083
|
-
|
|
90084
|
-
|
|
90674
|
+
{
|
|
90675
|
+
value: "send-notification",
|
|
90676
|
+
label: "send-notification",
|
|
90677
|
+
hint: "Send a message or run a script whose stdout becomes the message"
|
|
90678
|
+
},
|
|
90679
|
+
{ value: "run-script", label: "run-script", hint: "Execute a shell script silently" },
|
|
90680
|
+
{ value: "queue-quest", label: "queue-quest", hint: "Activate a quest" },
|
|
90681
|
+
{ value: "log-to-vault", label: "log-to-vault", hint: "Auto-log an activity and award XP" }
|
|
90085
90682
|
]
|
|
90086
90683
|
});
|
|
90087
90684
|
if (Ct(actionType))
|
|
@@ -90237,6 +90834,38 @@ async function forgeCreateCommand(ctx) {
|
|
|
90237
90834
|
}
|
|
90238
90835
|
}
|
|
90239
90836
|
}
|
|
90837
|
+
if (actionType === "run-script") {
|
|
90838
|
+
const script = await Ze({
|
|
90839
|
+
message: "Shell script to execute:",
|
|
90840
|
+
placeholder: "curl -s https://example.com/data | jq .price",
|
|
90841
|
+
validate: (value) => !value?.trim() ? "Script is required." : undefined
|
|
90842
|
+
});
|
|
90843
|
+
if (Ct(script))
|
|
90844
|
+
return bail();
|
|
90845
|
+
actionConfig.script = script.trim();
|
|
90846
|
+
const timeoutRaw = await Ze({
|
|
90847
|
+
message: "Timeout in seconds (default 30):",
|
|
90848
|
+
placeholder: "30",
|
|
90849
|
+
defaultValue: "30",
|
|
90850
|
+
validate: (value) => {
|
|
90851
|
+
const parsed = Number.parseInt(value ?? "", 10);
|
|
90852
|
+
if (!Number.isInteger(parsed) || parsed <= 0)
|
|
90853
|
+
return "Enter a positive integer.";
|
|
90854
|
+
return;
|
|
90855
|
+
}
|
|
90856
|
+
});
|
|
90857
|
+
if (Ct(timeoutRaw))
|
|
90858
|
+
return bail();
|
|
90859
|
+
actionConfig.timeout = Number.parseInt(String(timeoutRaw), 10) * 1000;
|
|
90860
|
+
const workdir = await Ze({
|
|
90861
|
+
message: "Working directory (optional):",
|
|
90862
|
+
placeholder: "~/projects/myapp"
|
|
90863
|
+
});
|
|
90864
|
+
if (Ct(workdir))
|
|
90865
|
+
return bail();
|
|
90866
|
+
if (workdir)
|
|
90867
|
+
actionConfig.workdir = workdir;
|
|
90868
|
+
}
|
|
90240
90869
|
const rule = await insertForgeRule(ctx.db, {
|
|
90241
90870
|
userId: ctx.user.id,
|
|
90242
90871
|
name: name21,
|
|
@@ -91774,6 +92403,7 @@ function showCommandHelp(command, sub) {
|
|
|
91774
92403
|
"Usage: grindxp companion <subcommand>",
|
|
91775
92404
|
"",
|
|
91776
92405
|
cmd("(none)", "Show companion settings"),
|
|
92406
|
+
cmd("trust [level]", "Show or set companion trust level (0\u20134)"),
|
|
91777
92407
|
cmd("soul", "Edit companion personality in $EDITOR"),
|
|
91778
92408
|
cmd("context [--refresh]", "View or refresh your user context"),
|
|
91779
92409
|
cmd("memory, insights", "List stored insights"),
|
|
@@ -92076,7 +92706,9 @@ async function main() {
|
|
|
92076
92706
|
}
|
|
92077
92707
|
break;
|
|
92078
92708
|
case "companion":
|
|
92079
|
-
if (sub === "
|
|
92709
|
+
if (sub === "trust") {
|
|
92710
|
+
await companionTrustCommand(ctx, rest[0]);
|
|
92711
|
+
} else if (sub === "soul") {
|
|
92080
92712
|
await companionSoulCommand(ctx);
|
|
92081
92713
|
} else if (sub === "context") {
|
|
92082
92714
|
await companionContextCommand(ctx, rest.includes("--refresh"));
|