@wellgrow/cli 0.1.3 → 0.2.0
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 +55 -679
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/manual/hooks.md +0 -61
package/dist/index.js
CHANGED
|
@@ -50,9 +50,6 @@ var wellGrowConfigSchema = z.object({
|
|
|
50
50
|
mcp: z.object({
|
|
51
51
|
paths: z.array(z.string())
|
|
52
52
|
}),
|
|
53
|
-
hooks: z.object({
|
|
54
|
-
paths: z.array(z.string())
|
|
55
|
-
}),
|
|
56
53
|
user: z.object({
|
|
57
54
|
name: z.string().optional()
|
|
58
55
|
}).optional(),
|
|
@@ -104,9 +101,6 @@ var DEFAULT_CONFIG = {
|
|
|
104
101
|
mcp: {
|
|
105
102
|
paths: []
|
|
106
103
|
},
|
|
107
|
-
hooks: {
|
|
108
|
-
paths: []
|
|
109
|
-
},
|
|
110
104
|
logging: {
|
|
111
105
|
verbose: false,
|
|
112
106
|
log_dir: "~/.wellgrow/logs"
|
|
@@ -505,15 +499,9 @@ function updateToolPart(parts, toolCallId, state, output, errorText) {
|
|
|
505
499
|
parts[idx] = { ...part, state, output, errorText };
|
|
506
500
|
}
|
|
507
501
|
}
|
|
508
|
-
function approvalDecisionToToolDecision(ad) {
|
|
509
|
-
if (ad.action === "allow") {
|
|
510
|
-
return { decision: "allow" };
|
|
511
|
-
}
|
|
512
|
-
return { decision: "deny", reason: "\u30E6\u30FC\u30B6\u30FC\u304C\u30C4\u30FC\u30EB\u306E\u5B9F\u884C\u3092\u62D2\u5426\u3057\u307E\u3057\u305F" };
|
|
513
|
-
}
|
|
514
502
|
async function requestApproval(tc, meta, pipeline, callbacks) {
|
|
515
503
|
if (!callbacks.onApprovalRequest) {
|
|
516
|
-
return
|
|
504
|
+
return true;
|
|
517
505
|
}
|
|
518
506
|
const decision = await callbacks.onApprovalRequest({
|
|
519
507
|
toolCallId: tc.toolCallId,
|
|
@@ -530,7 +518,7 @@ async function requestApproval(tc, meta, pipeline, callbacks) {
|
|
|
530
518
|
});
|
|
531
519
|
}
|
|
532
520
|
}
|
|
533
|
-
return
|
|
521
|
+
return decision.action === "allow";
|
|
534
522
|
}
|
|
535
523
|
function toToolOutput(value) {
|
|
536
524
|
if (typeof value === "string") {
|
|
@@ -539,7 +527,7 @@ function toToolOutput(value) {
|
|
|
539
527
|
return { type: "json", value };
|
|
540
528
|
}
|
|
541
529
|
async function executeSingleTool(tc, parts, config, callbacks, needsApproval) {
|
|
542
|
-
const { registry, pipeline
|
|
530
|
+
const { registry, pipeline } = config;
|
|
543
531
|
const handler = registry.handlers[tc.toolName];
|
|
544
532
|
if (!handler) {
|
|
545
533
|
const errorMsg = `\u4E0D\u660E\u306A\u30C4\u30FC\u30EB: ${tc.toolName}`;
|
|
@@ -552,72 +540,18 @@ async function executeSingleTool(tc, parts, config, callbacks, needsApproval) {
|
|
|
552
540
|
output: { type: "error-text", value: `Error: ${errorMsg}` }
|
|
553
541
|
};
|
|
554
542
|
}
|
|
555
|
-
const meta = registry.getMeta(tc.toolName);
|
|
556
|
-
const metaInfo = {
|
|
557
|
-
category: meta?.category ?? "execute",
|
|
558
|
-
source: meta?.source ?? "builtin"
|
|
559
|
-
};
|
|
560
|
-
if (executionHooks?.beforeExecute) {
|
|
561
|
-
const hookResult = await executionHooks.beforeExecute(tc, metaInfo);
|
|
562
|
-
if (hookResult.decision === "deny") {
|
|
563
|
-
updateToolPart(
|
|
564
|
-
parts,
|
|
565
|
-
tc.toolCallId,
|
|
566
|
-
"output-denied",
|
|
567
|
-
void 0,
|
|
568
|
-
hookResult.reason
|
|
569
|
-
);
|
|
570
|
-
callbacks.onMessageUpdate([...parts]);
|
|
571
|
-
return pipeline.createDeniedResult(
|
|
572
|
-
tc.toolCallId,
|
|
573
|
-
tc.toolName,
|
|
574
|
-
hookResult.reason
|
|
575
|
-
);
|
|
576
|
-
}
|
|
577
|
-
if (hookResult.decision === "ask") {
|
|
578
|
-
needsApproval = true;
|
|
579
|
-
}
|
|
580
|
-
if (hookResult.decision === "allow" && hookResult.updatedInput) {
|
|
581
|
-
tc = { ...tc, args: { ...tc.args, ...hookResult.updatedInput } };
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
543
|
if (needsApproval) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
if (hookDecision) {
|
|
592
|
-
if (hookDecision.behavior === "deny") {
|
|
593
|
-
const reason = "\u30D5\u30C3\u30AF\u306B\u3088\u308A\u62D2\u5426\u3055\u308C\u307E\u3057\u305F";
|
|
594
|
-
updateToolPart(parts, tc.toolCallId, "output-denied", void 0, reason);
|
|
595
|
-
callbacks.onMessageUpdate([...parts]);
|
|
596
|
-
return pipeline.createDeniedResult(tc.toolCallId, tc.toolName, reason);
|
|
597
|
-
}
|
|
598
|
-
if (hookDecision.updatedInput) {
|
|
599
|
-
tc = { ...tc, args: { ...tc.args, ...hookDecision.updatedInput } };
|
|
600
|
-
}
|
|
601
|
-
approved = true;
|
|
602
|
-
}
|
|
603
|
-
}
|
|
544
|
+
const meta = registry.getMeta(tc.toolName);
|
|
545
|
+
const metaInfo = {
|
|
546
|
+
category: meta?.category ?? "execute",
|
|
547
|
+
source: meta?.source ?? "builtin"
|
|
548
|
+
};
|
|
549
|
+
const approved = await requestApproval(tc, metaInfo, pipeline, callbacks);
|
|
604
550
|
if (!approved) {
|
|
605
|
-
const
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
tc.toolCallId,
|
|
610
|
-
"output-denied",
|
|
611
|
-
void 0,
|
|
612
|
-
approval.reason
|
|
613
|
-
);
|
|
614
|
-
callbacks.onMessageUpdate([...parts]);
|
|
615
|
-
return pipeline.createDeniedResult(
|
|
616
|
-
tc.toolCallId,
|
|
617
|
-
tc.toolName,
|
|
618
|
-
approval.reason
|
|
619
|
-
);
|
|
620
|
-
}
|
|
551
|
+
const reason = "\u30E6\u30FC\u30B6\u30FC\u304C\u30C4\u30FC\u30EB\u306E\u5B9F\u884C\u3092\u62D2\u5426\u3057\u307E\u3057\u305F";
|
|
552
|
+
updateToolPart(parts, tc.toolCallId, "output-denied", void 0, reason);
|
|
553
|
+
callbacks.onMessageUpdate([...parts]);
|
|
554
|
+
return pipeline.createDeniedResult(tc.toolCallId, tc.toolName, reason);
|
|
621
555
|
}
|
|
622
556
|
}
|
|
623
557
|
try {
|
|
@@ -631,6 +565,7 @@ async function executeSingleTool(tc, parts, config, callbacks, needsApproval) {
|
|
|
631
565
|
abortSignal: config.abortSignal
|
|
632
566
|
};
|
|
633
567
|
const resultPromise = handler(tc.args, handlerCtx);
|
|
568
|
+
const meta = registry.getMeta(tc.toolName);
|
|
634
569
|
const uiHooks = meta?.uiHooks;
|
|
635
570
|
const startEvent = uiHooks?.onStart?.(tc.args, tc.toolCallId);
|
|
636
571
|
if (startEvent && callbacks.onToolUIEvent) {
|
|
@@ -646,46 +581,21 @@ async function executeSingleTool(tc, parts, config, callbacks, needsApproval) {
|
|
|
646
581
|
if (completeEvent && callbacks.onToolUIEvent) {
|
|
647
582
|
callbacks.onToolUIEvent(completeEvent);
|
|
648
583
|
}
|
|
649
|
-
const afterResult = await executionHooks?.afterExecute?.(tc, {
|
|
650
|
-
success: true,
|
|
651
|
-
output: toolResult
|
|
652
|
-
});
|
|
653
|
-
const output = toToolOutput(toolResult);
|
|
654
|
-
if (afterResult?.feedback) {
|
|
655
|
-
const feedbackOutput = toToolOutput(
|
|
656
|
-
`${typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult)}
|
|
657
|
-
|
|
658
|
-
[Hook feedback]: ${afterResult.feedback}`
|
|
659
|
-
);
|
|
660
|
-
return {
|
|
661
|
-
type: "tool-result",
|
|
662
|
-
toolCallId: tc.toolCallId,
|
|
663
|
-
toolName: tc.toolName,
|
|
664
|
-
output: feedbackOutput
|
|
665
|
-
};
|
|
666
|
-
}
|
|
667
584
|
return {
|
|
668
585
|
type: "tool-result",
|
|
669
586
|
toolCallId: tc.toolCallId,
|
|
670
587
|
toolName: tc.toolName,
|
|
671
|
-
output
|
|
588
|
+
output: toToolOutput(toolResult)
|
|
672
589
|
};
|
|
673
590
|
} catch (error) {
|
|
674
591
|
const errorMsg = formatToolError(error);
|
|
675
592
|
updateToolPart(parts, tc.toolCallId, "output-error", void 0, errorMsg);
|
|
676
593
|
callbacks.onMessageUpdate([...parts]);
|
|
677
|
-
const afterResult = await executionHooks?.afterExecute?.(tc, {
|
|
678
|
-
success: false,
|
|
679
|
-
error: errorMsg
|
|
680
|
-
});
|
|
681
|
-
const errorOutput = afterResult?.feedback ? `${errorMsg}
|
|
682
|
-
|
|
683
|
-
[Hook feedback]: ${afterResult.feedback}` : errorMsg;
|
|
684
594
|
return {
|
|
685
595
|
type: "tool-result",
|
|
686
596
|
toolCallId: tc.toolCallId,
|
|
687
597
|
toolName: tc.toolName,
|
|
688
|
-
output: { type: "error-text", value:
|
|
598
|
+
output: { type: "error-text", value: errorMsg }
|
|
689
599
|
};
|
|
690
600
|
}
|
|
691
601
|
}
|
|
@@ -749,17 +659,6 @@ async function runAgentLoop(messages, config, callbacks) {
|
|
|
749
659
|
const turnResult = await executeTurn(messages, config, callbacks, parts);
|
|
750
660
|
fullText += turnResult.text;
|
|
751
661
|
if (turnResult.finishReason !== "tool-calls") {
|
|
752
|
-
if (config.onStop) {
|
|
753
|
-
const stopResult = await config.onStop(turnResult.text);
|
|
754
|
-
if (stopResult.blocked && stopResult.reason) {
|
|
755
|
-
messages.push({
|
|
756
|
-
role: "user",
|
|
757
|
-
content: stopResult.reason
|
|
758
|
-
});
|
|
759
|
-
turnsUsed++;
|
|
760
|
-
continue;
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
662
|
break;
|
|
764
663
|
}
|
|
765
664
|
const toolResults = await executeToolCalls(
|
|
@@ -1089,7 +988,7 @@ function createSessionContext() {
|
|
|
1089
988
|
}
|
|
1090
989
|
|
|
1091
990
|
// src/agents/resolver.ts
|
|
1092
|
-
import { join as
|
|
991
|
+
import { join as join9 } from "path";
|
|
1093
992
|
|
|
1094
993
|
// src/agents/loader.ts
|
|
1095
994
|
import { readFile as readFile2, readdir } from "fs/promises";
|
|
@@ -1116,9 +1015,6 @@ var agentConfigSchema = z3.object({
|
|
|
1116
1015
|
}).optional(),
|
|
1117
1016
|
skills: z3.object({
|
|
1118
1017
|
paths: z3.array(z3.string()).optional()
|
|
1119
|
-
}).optional(),
|
|
1120
|
-
hooks: z3.object({
|
|
1121
|
-
paths: z3.array(z3.string()).optional()
|
|
1122
1018
|
}).optional()
|
|
1123
1019
|
});
|
|
1124
1020
|
|
|
@@ -1621,10 +1517,8 @@ var backgroundProcesses = /* @__PURE__ */ new Set();
|
|
|
1621
1517
|
var isShuttingDown = false;
|
|
1622
1518
|
var lastSigintTime = 0;
|
|
1623
1519
|
var activeCtx = null;
|
|
1624
|
-
|
|
1625
|
-
function setActiveSession(ctx, hookEngine) {
|
|
1520
|
+
function setActiveSession(ctx) {
|
|
1626
1521
|
activeCtx = ctx;
|
|
1627
|
-
activeHookEngine = hookEngine;
|
|
1628
1522
|
}
|
|
1629
1523
|
function registerShutdownHandler(handler) {
|
|
1630
1524
|
shutdownHandlers.push(handler);
|
|
@@ -1683,23 +1577,11 @@ async function cleanupMcpClients() {
|
|
|
1683
1577
|
} catch {
|
|
1684
1578
|
}
|
|
1685
1579
|
}
|
|
1686
|
-
async function fireSessionEndHook(reason) {
|
|
1687
|
-
if (!activeHookEngine) return;
|
|
1688
|
-
try {
|
|
1689
|
-
await Promise.race([
|
|
1690
|
-
activeHookEngine.fire("SessionEnd", { reason }),
|
|
1691
|
-
new Promise((r) => setTimeout(r, 5e3))
|
|
1692
|
-
]);
|
|
1693
|
-
} catch {
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
1580
|
async function gracefulShutdown(_reason) {
|
|
1697
1581
|
if (isShuttingDown) {
|
|
1698
1582
|
process.exit(1);
|
|
1699
1583
|
}
|
|
1700
1584
|
isShuttingDown = true;
|
|
1701
|
-
const hookReason = _reason === "sigint" || _reason === "sigint_double" ? "prompt_input_exit" : "other";
|
|
1702
|
-
await fireSessionEndHook(hookReason);
|
|
1703
1585
|
for (const handler of shutdownHandlers) {
|
|
1704
1586
|
try {
|
|
1705
1587
|
await Promise.race([handler(), new Promise((r) => setTimeout(r, 5e3))]);
|
|
@@ -3099,455 +2981,6 @@ async function discoverSkills(paths) {
|
|
|
3099
2981
|
return skills;
|
|
3100
2982
|
}
|
|
3101
2983
|
|
|
3102
|
-
// src/extensions/hook-engine.ts
|
|
3103
|
-
import { readFile as readFile9, readdir as readdir4, stat as stat3 } from "fs/promises";
|
|
3104
|
-
import { join as join9 } from "path";
|
|
3105
|
-
import { homedir as homedir9 } from "os";
|
|
3106
|
-
import { spawn as spawn2 } from "child_process";
|
|
3107
|
-
import { z as z11 } from "zod";
|
|
3108
|
-
import { generateText } from "ai";
|
|
3109
|
-
var hookDefinitionSchema = z11.discriminatedUnion("type", [
|
|
3110
|
-
z11.object({
|
|
3111
|
-
type: z11.literal("command"),
|
|
3112
|
-
command: z11.string(),
|
|
3113
|
-
timeout: z11.number().positive().optional(),
|
|
3114
|
-
async: z11.boolean().optional(),
|
|
3115
|
-
statusMessage: z11.string().optional()
|
|
3116
|
-
}),
|
|
3117
|
-
z11.object({
|
|
3118
|
-
type: z11.literal("prompt"),
|
|
3119
|
-
prompt: z11.string(),
|
|
3120
|
-
model: z11.string().optional(),
|
|
3121
|
-
timeout: z11.number().positive().optional()
|
|
3122
|
-
})
|
|
3123
|
-
]);
|
|
3124
|
-
var hookGroupSchema = z11.object({
|
|
3125
|
-
matcher: z11.string().optional(),
|
|
3126
|
-
hooks: z11.array(hookDefinitionSchema)
|
|
3127
|
-
});
|
|
3128
|
-
var hooksFileConfigSchema = z11.object({
|
|
3129
|
-
hooks: z11.record(z11.array(hookGroupSchema)).default({})
|
|
3130
|
-
});
|
|
3131
|
-
var DEFAULT_HOOK_MODEL = "claude-haiku-4-5-20251001";
|
|
3132
|
-
var DEFAULT_COMMAND_TIMEOUT_SEC = 30;
|
|
3133
|
-
var MAX_HOOK_OUTPUT_BYTES = 1024 * 1024;
|
|
3134
|
-
function expandTilde2(p) {
|
|
3135
|
-
return p.startsWith("~") ? join9(homedir9(), p.slice(1)) : p;
|
|
3136
|
-
}
|
|
3137
|
-
function hookKey(hook) {
|
|
3138
|
-
switch (hook.type) {
|
|
3139
|
-
case "command":
|
|
3140
|
-
return `command:${hook.command}:async=${hook.async ?? false}`;
|
|
3141
|
-
case "prompt":
|
|
3142
|
-
return `prompt:${hook.prompt}:${hook.model ?? "default"}`;
|
|
3143
|
-
}
|
|
3144
|
-
}
|
|
3145
|
-
function warnHook(message) {
|
|
3146
|
-
process.stderr.write(`[hooks] ${message}
|
|
3147
|
-
`);
|
|
3148
|
-
}
|
|
3149
|
-
async function loadHooksFile(filePath) {
|
|
3150
|
-
let content;
|
|
3151
|
-
try {
|
|
3152
|
-
content = await readFile9(expandTilde2(filePath), "utf-8");
|
|
3153
|
-
} catch {
|
|
3154
|
-
return { hooks: {} };
|
|
3155
|
-
}
|
|
3156
|
-
let raw;
|
|
3157
|
-
try {
|
|
3158
|
-
raw = JSON.parse(content);
|
|
3159
|
-
} catch (error) {
|
|
3160
|
-
warnHook(
|
|
3161
|
-
`${filePath} \u306EJSON\u89E3\u6790\u306B\u5931\u6557: ${error instanceof Error ? error.message : String(error)}`
|
|
3162
|
-
);
|
|
3163
|
-
return { hooks: {} };
|
|
3164
|
-
}
|
|
3165
|
-
const result = hooksFileConfigSchema.safeParse(raw);
|
|
3166
|
-
if (!result.success) {
|
|
3167
|
-
const issues = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
|
|
3168
|
-
warnHook(`${filePath} \u306E\u30B9\u30AD\u30FC\u30DE\u304C\u4E0D\u6B63: ${issues}`);
|
|
3169
|
-
return { hooks: {} };
|
|
3170
|
-
}
|
|
3171
|
-
return { hooks: result.data.hooks };
|
|
3172
|
-
}
|
|
3173
|
-
async function resolveHookFiles(pathInput) {
|
|
3174
|
-
const resolvedPath = expandTilde2(pathInput);
|
|
3175
|
-
let stats;
|
|
3176
|
-
try {
|
|
3177
|
-
stats = await stat3(resolvedPath);
|
|
3178
|
-
} catch {
|
|
3179
|
-
return [];
|
|
3180
|
-
}
|
|
3181
|
-
if (stats.isFile()) {
|
|
3182
|
-
return [resolvedPath];
|
|
3183
|
-
}
|
|
3184
|
-
if (!stats.isDirectory()) {
|
|
3185
|
-
return [];
|
|
3186
|
-
}
|
|
3187
|
-
let entries;
|
|
3188
|
-
try {
|
|
3189
|
-
entries = await readdir4(resolvedPath, { withFileTypes: true });
|
|
3190
|
-
} catch {
|
|
3191
|
-
return [];
|
|
3192
|
-
}
|
|
3193
|
-
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".json")).map((entry) => join9(resolvedPath, entry.name)).sort((a, b) => a.localeCompare(b));
|
|
3194
|
-
}
|
|
3195
|
-
function mergeHooksConfig(target, source) {
|
|
3196
|
-
const events = Object.keys(source.hooks);
|
|
3197
|
-
for (const event of events) {
|
|
3198
|
-
const sourceGroups = source.hooks[event] ?? [];
|
|
3199
|
-
const currentGroups = target.hooks[event] ?? [];
|
|
3200
|
-
target.hooks[event] = [...currentGroups, ...sourceGroups];
|
|
3201
|
-
}
|
|
3202
|
-
}
|
|
3203
|
-
async function loadHooksConfig(pathsOrGlobalPath, agentPath) {
|
|
3204
|
-
const pathList = Array.isArray(pathsOrGlobalPath) ? pathsOrGlobalPath : [pathsOrGlobalPath, ...agentPath ? [agentPath] : []];
|
|
3205
|
-
const merged = { hooks: {} };
|
|
3206
|
-
for (const pathEntry of pathList) {
|
|
3207
|
-
const files = await resolveHookFiles(pathEntry);
|
|
3208
|
-
for (const filePath of files) {
|
|
3209
|
-
const config = await loadHooksFile(filePath);
|
|
3210
|
-
mergeHooksConfig(merged, config);
|
|
3211
|
-
}
|
|
3212
|
-
}
|
|
3213
|
-
return merged;
|
|
3214
|
-
}
|
|
3215
|
-
var regexCache = /* @__PURE__ */ new Map();
|
|
3216
|
-
function getCachedRegex(pattern) {
|
|
3217
|
-
const cached = regexCache.get(pattern);
|
|
3218
|
-
if (cached !== void 0) return cached;
|
|
3219
|
-
try {
|
|
3220
|
-
const regex = new RegExp(`^(?:${pattern})$`);
|
|
3221
|
-
regexCache.set(pattern, regex);
|
|
3222
|
-
return regex;
|
|
3223
|
-
} catch {
|
|
3224
|
-
regexCache.set(pattern, null);
|
|
3225
|
-
return null;
|
|
3226
|
-
}
|
|
3227
|
-
}
|
|
3228
|
-
function matchesTarget(matcher, target) {
|
|
3229
|
-
if (!matcher || matcher === "" || matcher === "*") return true;
|
|
3230
|
-
if (target === null) return true;
|
|
3231
|
-
const regex = getCachedRegex(matcher);
|
|
3232
|
-
if (regex) return regex.test(target);
|
|
3233
|
-
return matcher === target;
|
|
3234
|
-
}
|
|
3235
|
-
function getMatchTarget(event, input) {
|
|
3236
|
-
switch (event) {
|
|
3237
|
-
case "PreToolUse":
|
|
3238
|
-
case "PostToolUse":
|
|
3239
|
-
case "PostToolUseFailure":
|
|
3240
|
-
case "PermissionRequest":
|
|
3241
|
-
return input.tool_name ?? null;
|
|
3242
|
-
case "SessionStart":
|
|
3243
|
-
return input.source ?? null;
|
|
3244
|
-
case "SessionEnd":
|
|
3245
|
-
return input.reason ?? null;
|
|
3246
|
-
case "UserPromptSubmit":
|
|
3247
|
-
case "Stop":
|
|
3248
|
-
return null;
|
|
3249
|
-
}
|
|
3250
|
-
}
|
|
3251
|
-
function executeCommandHook(hook, input) {
|
|
3252
|
-
return new Promise((resolve5) => {
|
|
3253
|
-
const timeoutMs = (hook.timeout ?? DEFAULT_COMMAND_TIMEOUT_SEC) * 1e3;
|
|
3254
|
-
const command = expandTilde2(hook.command);
|
|
3255
|
-
const child = spawn2("sh", ["-c", command], {
|
|
3256
|
-
env: { ...process.env },
|
|
3257
|
-
timeout: timeoutMs,
|
|
3258
|
-
stdio: ["pipe", "pipe", "pipe"]
|
|
3259
|
-
});
|
|
3260
|
-
let stdout = "";
|
|
3261
|
-
let stderr = "";
|
|
3262
|
-
child.stdout.on("data", (data) => {
|
|
3263
|
-
if (stdout.length < MAX_HOOK_OUTPUT_BYTES) {
|
|
3264
|
-
stdout += data.toString();
|
|
3265
|
-
}
|
|
3266
|
-
});
|
|
3267
|
-
child.stderr.on("data", (data) => {
|
|
3268
|
-
if (stderr.length < MAX_HOOK_OUTPUT_BYTES) {
|
|
3269
|
-
stderr += data.toString();
|
|
3270
|
-
}
|
|
3271
|
-
});
|
|
3272
|
-
child.stdin.on("error", () => {
|
|
3273
|
-
});
|
|
3274
|
-
child.stdin.write(JSON.stringify(input));
|
|
3275
|
-
child.stdin.end();
|
|
3276
|
-
child.on("close", (code) => {
|
|
3277
|
-
resolve5({ stdout, stderr, exitCode: code ?? 1 });
|
|
3278
|
-
});
|
|
3279
|
-
child.on("error", (err) => {
|
|
3280
|
-
warnHook(`\u30B3\u30DE\u30F3\u30C9hook\u5B9F\u884C\u30A8\u30E9\u30FC (${hook.command}): ${err.message}`);
|
|
3281
|
-
resolve5({ stdout, stderr, exitCode: 1 });
|
|
3282
|
-
});
|
|
3283
|
-
});
|
|
3284
|
-
}
|
|
3285
|
-
async function executeLLMHook(hook, input, resolveModel) {
|
|
3286
|
-
const prompt = hook.prompt.replace(/\$ARGUMENTS/g, JSON.stringify(input));
|
|
3287
|
-
const model = resolveModel(hook.model);
|
|
3288
|
-
try {
|
|
3289
|
-
const result = await generateText({
|
|
3290
|
-
model,
|
|
3291
|
-
prompt,
|
|
3292
|
-
maxOutputTokens: 500,
|
|
3293
|
-
...hook.timeout ? { abortSignal: AbortSignal.timeout(hook.timeout * 1e3) } : {}
|
|
3294
|
-
});
|
|
3295
|
-
const text = result.text.trim();
|
|
3296
|
-
try {
|
|
3297
|
-
const parsed = JSON.parse(text);
|
|
3298
|
-
return { ok: parsed.ok !== false, reason: parsed.reason };
|
|
3299
|
-
} catch {
|
|
3300
|
-
warnHook(
|
|
3301
|
-
`Prompt hook\u306E\u30EC\u30B9\u30DD\u30F3\u30B9\u304CJSON\u5F62\u5F0F\u3067\u306F\u3042\u308A\u307E\u305B\u3093: ${text.slice(0, 200)}`
|
|
3302
|
-
);
|
|
3303
|
-
return { ok: true };
|
|
3304
|
-
}
|
|
3305
|
-
} catch (error) {
|
|
3306
|
-
warnHook(
|
|
3307
|
-
`Prompt hook\u5B9F\u884C\u30A8\u30E9\u30FC: ${error instanceof Error ? error.message : String(error)}`
|
|
3308
|
-
);
|
|
3309
|
-
return { ok: true };
|
|
3310
|
-
}
|
|
3311
|
-
}
|
|
3312
|
-
function normalizeCommandOutput(event, stdout, stderr, exitCode) {
|
|
3313
|
-
if (exitCode === 2) {
|
|
3314
|
-
return {
|
|
3315
|
-
blocked: true,
|
|
3316
|
-
reason: stderr.trim() || "\u30D5\u30C3\u30AF\u306B\u3088\u308A\u30D6\u30ED\u30C3\u30AF\u3055\u308C\u307E\u3057\u305F",
|
|
3317
|
-
feedback: stderr.trim() || void 0
|
|
3318
|
-
};
|
|
3319
|
-
}
|
|
3320
|
-
if (exitCode !== 0) {
|
|
3321
|
-
return { blocked: false };
|
|
3322
|
-
}
|
|
3323
|
-
const trimmed = stdout.trim();
|
|
3324
|
-
let parsed;
|
|
3325
|
-
try {
|
|
3326
|
-
parsed = JSON.parse(trimmed);
|
|
3327
|
-
} catch {
|
|
3328
|
-
if (event === "SessionStart" && trimmed) {
|
|
3329
|
-
return { blocked: false, additionalContext: trimmed };
|
|
3330
|
-
}
|
|
3331
|
-
return { blocked: false };
|
|
3332
|
-
}
|
|
3333
|
-
const hso = parsed.hookSpecificOutput;
|
|
3334
|
-
switch (event) {
|
|
3335
|
-
case "PreToolUse": {
|
|
3336
|
-
const decision = hso?.permissionDecision;
|
|
3337
|
-
if (decision === "deny") {
|
|
3338
|
-
return {
|
|
3339
|
-
blocked: true,
|
|
3340
|
-
reason: hso?.permissionDecisionReason ?? "\u30D5\u30C3\u30AF\u306B\u3088\u308A\u30D6\u30ED\u30C3\u30AF\u3055\u308C\u307E\u3057\u305F"
|
|
3341
|
-
};
|
|
3342
|
-
}
|
|
3343
|
-
if (decision === "ask") {
|
|
3344
|
-
return { blocked: false, askUser: true };
|
|
3345
|
-
}
|
|
3346
|
-
return {
|
|
3347
|
-
blocked: false,
|
|
3348
|
-
updatedInput: hso?.updatedInput,
|
|
3349
|
-
additionalContext: hso?.additionalContext
|
|
3350
|
-
};
|
|
3351
|
-
}
|
|
3352
|
-
case "PermissionRequest": {
|
|
3353
|
-
const decision = hso?.decision;
|
|
3354
|
-
if (decision?.behavior === "deny") {
|
|
3355
|
-
return { blocked: true, reason: "\u30D5\u30C3\u30AF\u306B\u3088\u308A\u62D2\u5426\u3055\u308C\u307E\u3057\u305F" };
|
|
3356
|
-
}
|
|
3357
|
-
if (decision?.behavior === "allow") {
|
|
3358
|
-
return { blocked: false, updatedInput: decision.updatedInput };
|
|
3359
|
-
}
|
|
3360
|
-
return { blocked: false };
|
|
3361
|
-
}
|
|
3362
|
-
case "Stop": {
|
|
3363
|
-
if (parsed.decision === "block") {
|
|
3364
|
-
return {
|
|
3365
|
-
blocked: true,
|
|
3366
|
-
stopDecision: "block",
|
|
3367
|
-
reason: parsed.reason ?? "\u30D5\u30C3\u30AF\u306B\u3088\u308A\u7D9A\u884C\u304C\u8981\u6C42\u3055\u308C\u307E\u3057\u305F"
|
|
3368
|
-
};
|
|
3369
|
-
}
|
|
3370
|
-
return { blocked: false, stopDecision: "approve" };
|
|
3371
|
-
}
|
|
3372
|
-
case "SessionStart": {
|
|
3373
|
-
const ctx = hso?.additionalContext ?? trimmed;
|
|
3374
|
-
return { blocked: false, additionalContext: ctx || void 0 };
|
|
3375
|
-
}
|
|
3376
|
-
default:
|
|
3377
|
-
return { blocked: false };
|
|
3378
|
-
}
|
|
3379
|
-
}
|
|
3380
|
-
function normalizePromptOutput(ok, reason) {
|
|
3381
|
-
if (!ok) {
|
|
3382
|
-
return {
|
|
3383
|
-
blocked: true,
|
|
3384
|
-
reason: reason ?? "\u30D5\u30C3\u30AF\u306B\u3088\u308A\u30D6\u30ED\u30C3\u30AF\u3055\u308C\u307E\u3057\u305F"
|
|
3385
|
-
};
|
|
3386
|
-
}
|
|
3387
|
-
return { blocked: false };
|
|
3388
|
-
}
|
|
3389
|
-
function mergeResults(results) {
|
|
3390
|
-
const merged = { blocked: false };
|
|
3391
|
-
for (const r of results) {
|
|
3392
|
-
if (r.blocked) {
|
|
3393
|
-
merged.blocked = true;
|
|
3394
|
-
merged.reason = r.reason ?? merged.reason;
|
|
3395
|
-
}
|
|
3396
|
-
if (r.stopDecision) merged.stopDecision = r.stopDecision;
|
|
3397
|
-
if (r.askUser) merged.askUser = true;
|
|
3398
|
-
if (r.feedback) {
|
|
3399
|
-
merged.feedback = merged.feedback ? `${merged.feedback}
|
|
3400
|
-
${r.feedback}` : r.feedback;
|
|
3401
|
-
}
|
|
3402
|
-
if (r.additionalContext) {
|
|
3403
|
-
merged.additionalContext = merged.additionalContext ? `${merged.additionalContext}
|
|
3404
|
-
${r.additionalContext}` : r.additionalContext;
|
|
3405
|
-
}
|
|
3406
|
-
if (r.updatedInput) {
|
|
3407
|
-
merged.updatedInput = { ...merged.updatedInput, ...r.updatedInput };
|
|
3408
|
-
}
|
|
3409
|
-
}
|
|
3410
|
-
return merged;
|
|
3411
|
-
}
|
|
3412
|
-
var HookEngine = class {
|
|
3413
|
-
hooksConfig;
|
|
3414
|
-
options;
|
|
3415
|
-
_hasHooks;
|
|
3416
|
-
constructor(hooksConfig, options) {
|
|
3417
|
-
this.hooksConfig = hooksConfig;
|
|
3418
|
-
this.options = options;
|
|
3419
|
-
this._hasHooks = Object.keys(hooksConfig.hooks).length > 0;
|
|
3420
|
-
}
|
|
3421
|
-
get hasHooks() {
|
|
3422
|
-
return this._hasHooks;
|
|
3423
|
-
}
|
|
3424
|
-
async fire(event, eventInput = {}) {
|
|
3425
|
-
const groups = this.hooksConfig.hooks[event];
|
|
3426
|
-
if (!groups || groups.length === 0) {
|
|
3427
|
-
return { blocked: false };
|
|
3428
|
-
}
|
|
3429
|
-
const input = {
|
|
3430
|
-
session_id: this.options.sessionId,
|
|
3431
|
-
cwd: this.options.cwd,
|
|
3432
|
-
hook_event_name: event,
|
|
3433
|
-
...eventInput
|
|
3434
|
-
};
|
|
3435
|
-
const matchTarget = getMatchTarget(event, input);
|
|
3436
|
-
const matchingGroups = groups.filter(
|
|
3437
|
-
(g) => matchesTarget(g.matcher, matchTarget)
|
|
3438
|
-
);
|
|
3439
|
-
if (matchingGroups.length === 0) {
|
|
3440
|
-
return { blocked: false };
|
|
3441
|
-
}
|
|
3442
|
-
const seen = /* @__PURE__ */ new Set();
|
|
3443
|
-
const syncHooks = [];
|
|
3444
|
-
const asyncHooks = [];
|
|
3445
|
-
for (const group of matchingGroups) {
|
|
3446
|
-
for (const hook of group.hooks) {
|
|
3447
|
-
const key = hookKey(hook);
|
|
3448
|
-
if (seen.has(key)) continue;
|
|
3449
|
-
seen.add(key);
|
|
3450
|
-
if (hook.type === "command" && hook.async) {
|
|
3451
|
-
asyncHooks.push(hook);
|
|
3452
|
-
} else {
|
|
3453
|
-
syncHooks.push(hook);
|
|
3454
|
-
}
|
|
3455
|
-
}
|
|
3456
|
-
}
|
|
3457
|
-
for (const hook of asyncHooks) {
|
|
3458
|
-
this.executeOne(hook, event, input).catch((error) => {
|
|
3459
|
-
warnHook(
|
|
3460
|
-
`\u975E\u540C\u671Fhook\u5B9F\u884C\u30A8\u30E9\u30FC (${hook.command}): ${error instanceof Error ? error.message : String(error)}`
|
|
3461
|
-
);
|
|
3462
|
-
});
|
|
3463
|
-
}
|
|
3464
|
-
if (syncHooks.length === 0) {
|
|
3465
|
-
return { blocked: false };
|
|
3466
|
-
}
|
|
3467
|
-
const results = await Promise.all(
|
|
3468
|
-
syncHooks.map((hook) => this.executeOne(hook, event, input))
|
|
3469
|
-
);
|
|
3470
|
-
return mergeResults(results);
|
|
3471
|
-
}
|
|
3472
|
-
// ----- Bridge to ToolExecutionHooks (agent-loop.ts) -----
|
|
3473
|
-
createToolExecutionHooks() {
|
|
3474
|
-
return {
|
|
3475
|
-
beforeExecute: async (toolCall, _meta) => {
|
|
3476
|
-
const result = await this.fire("PreToolUse", {
|
|
3477
|
-
tool_name: toolCall.toolName,
|
|
3478
|
-
tool_input: toolCall.args,
|
|
3479
|
-
tool_use_id: toolCall.toolCallId
|
|
3480
|
-
});
|
|
3481
|
-
if (result.blocked) {
|
|
3482
|
-
return {
|
|
3483
|
-
decision: "deny",
|
|
3484
|
-
reason: result.reason ?? "\u30D5\u30C3\u30AF\u306B\u3088\u308A\u30D6\u30ED\u30C3\u30AF\u3055\u308C\u307E\u3057\u305F"
|
|
3485
|
-
};
|
|
3486
|
-
}
|
|
3487
|
-
if (result.askUser) {
|
|
3488
|
-
return { decision: "ask" };
|
|
3489
|
-
}
|
|
3490
|
-
return {
|
|
3491
|
-
decision: "allow",
|
|
3492
|
-
updatedInput: result.updatedInput,
|
|
3493
|
-
additionalContext: result.additionalContext
|
|
3494
|
-
};
|
|
3495
|
-
},
|
|
3496
|
-
afterExecute: async (toolCall, result) => {
|
|
3497
|
-
const event = result.success ? "PostToolUse" : "PostToolUseFailure";
|
|
3498
|
-
const hookResult = await this.fire(event, {
|
|
3499
|
-
tool_name: toolCall.toolName,
|
|
3500
|
-
tool_input: toolCall.args,
|
|
3501
|
-
tool_use_id: toolCall.toolCallId,
|
|
3502
|
-
...result.success ? { tool_response: result.output } : { error: result.error }
|
|
3503
|
-
});
|
|
3504
|
-
if (hookResult.feedback) {
|
|
3505
|
-
return { feedback: hookResult.feedback };
|
|
3506
|
-
}
|
|
3507
|
-
},
|
|
3508
|
-
onPermissionRequest: async (toolCall, _meta) => {
|
|
3509
|
-
const result = await this.fire("PermissionRequest", {
|
|
3510
|
-
tool_name: toolCall.toolName,
|
|
3511
|
-
tool_input: toolCall.args
|
|
3512
|
-
});
|
|
3513
|
-
if (result.blocked) {
|
|
3514
|
-
return { behavior: "deny" };
|
|
3515
|
-
}
|
|
3516
|
-
if (result.updatedInput !== void 0) {
|
|
3517
|
-
return {
|
|
3518
|
-
behavior: "allow",
|
|
3519
|
-
updatedInput: result.updatedInput
|
|
3520
|
-
};
|
|
3521
|
-
}
|
|
3522
|
-
return null;
|
|
3523
|
-
}
|
|
3524
|
-
};
|
|
3525
|
-
}
|
|
3526
|
-
// ----- Private helpers -----
|
|
3527
|
-
async executeOne(hook, event, input) {
|
|
3528
|
-
switch (hook.type) {
|
|
3529
|
-
case "command": {
|
|
3530
|
-
const { stdout, stderr, exitCode } = await executeCommandHook(
|
|
3531
|
-
hook,
|
|
3532
|
-
input
|
|
3533
|
-
);
|
|
3534
|
-
return normalizeCommandOutput(event, stdout, stderr, exitCode);
|
|
3535
|
-
}
|
|
3536
|
-
case "prompt": {
|
|
3537
|
-
const { ok, reason } = await executeLLMHook(
|
|
3538
|
-
hook,
|
|
3539
|
-
input,
|
|
3540
|
-
(id) => this.resolveModel(id)
|
|
3541
|
-
);
|
|
3542
|
-
return normalizePromptOutput(ok, reason);
|
|
3543
|
-
}
|
|
3544
|
-
}
|
|
3545
|
-
}
|
|
3546
|
-
resolveModel(modelId) {
|
|
3547
|
-
return getModel(modelId ?? DEFAULT_HOOK_MODEL, this.options.config);
|
|
3548
|
-
}
|
|
3549
|
-
};
|
|
3550
|
-
|
|
3551
2984
|
// src/agents/resolver.ts
|
|
3552
2985
|
async function resolveAgent(options, sessionCtx) {
|
|
3553
2986
|
const config = options.config ?? await loadConfig();
|
|
@@ -3576,7 +3009,7 @@ async function resolveAgent(options, sessionCtx) {
|
|
|
3576
3009
|
environment
|
|
3577
3010
|
});
|
|
3578
3011
|
const skillPaths = [
|
|
3579
|
-
|
|
3012
|
+
join9(agentDir, "skills"),
|
|
3580
3013
|
...config.skills.paths,
|
|
3581
3014
|
...agentConfig.skills?.paths ?? []
|
|
3582
3015
|
];
|
|
@@ -3620,19 +3053,6 @@ async function resolveAgent(options, sessionCtx) {
|
|
|
3620
3053
|
mode,
|
|
3621
3054
|
allowedMcps: config.permissions.allowed_mcps
|
|
3622
3055
|
});
|
|
3623
|
-
const hookPaths = [
|
|
3624
|
-
...config.hooks.paths,
|
|
3625
|
-
...agentConfig.hooks?.paths ?? []
|
|
3626
|
-
];
|
|
3627
|
-
const hooksConfig = await loadHooksConfig(hookPaths);
|
|
3628
|
-
let hookEngine = null;
|
|
3629
|
-
if (Object.keys(hooksConfig.hooks).length > 0) {
|
|
3630
|
-
hookEngine = new HookEngine(hooksConfig, {
|
|
3631
|
-
sessionId: sessionCtx.sessionId,
|
|
3632
|
-
cwd: sessionCtx.cwd,
|
|
3633
|
-
config
|
|
3634
|
-
});
|
|
3635
|
-
}
|
|
3636
3056
|
let finalSystemPrompt = systemPrompt;
|
|
3637
3057
|
if (mcpFailures.length > 0) {
|
|
3638
3058
|
finalSystemPrompt += `
|
|
@@ -3652,7 +3072,6 @@ ${mcpFailures.map((f) => `- ${f}`).join("\n")}
|
|
|
3652
3072
|
registry,
|
|
3653
3073
|
pipeline,
|
|
3654
3074
|
mcpManager,
|
|
3655
|
-
hookEngine,
|
|
3656
3075
|
maxTurns
|
|
3657
3076
|
};
|
|
3658
3077
|
}
|
|
@@ -3672,19 +3091,9 @@ async function createSession(options) {
|
|
|
3672
3091
|
},
|
|
3673
3092
|
ctx
|
|
3674
3093
|
);
|
|
3675
|
-
if (agent.hookEngine) {
|
|
3676
|
-
await agent.hookEngine.fire("SessionStart", {
|
|
3677
|
-
source: options.sessionStartSource ?? "startup"
|
|
3678
|
-
});
|
|
3679
|
-
}
|
|
3680
3094
|
return { ctx, messages: [], agent };
|
|
3681
3095
|
}
|
|
3682
3096
|
async function switchAgent(session, newAgentName, options) {
|
|
3683
|
-
if (session.agent.hookEngine) {
|
|
3684
|
-
await session.agent.hookEngine.fire("SessionEnd", {
|
|
3685
|
-
reason: "agent_switch"
|
|
3686
|
-
});
|
|
3687
|
-
}
|
|
3688
3097
|
if (session.agent.mcpManager) {
|
|
3689
3098
|
await session.agent.mcpManager.disconnectAll();
|
|
3690
3099
|
session.ctx.mcpManager = null;
|
|
@@ -3702,30 +3111,7 @@ async function switchAgent(session, newAgentName, options) {
|
|
|
3702
3111
|
);
|
|
3703
3112
|
}
|
|
3704
3113
|
async function sendMessage(session, userMessage, callbacks, options) {
|
|
3705
|
-
const hookEngine = session.agent.hookEngine;
|
|
3706
|
-
if (hookEngine) {
|
|
3707
|
-
const submitResult = await hookEngine.fire("UserPromptSubmit", {
|
|
3708
|
-
prompt: userMessage
|
|
3709
|
-
});
|
|
3710
|
-
if (submitResult.blocked) {
|
|
3711
|
-
return {
|
|
3712
|
-
fullText: submitResult.reason ?? "",
|
|
3713
|
-
parts: []
|
|
3714
|
-
};
|
|
3715
|
-
}
|
|
3716
|
-
}
|
|
3717
3114
|
session.messages.push({ role: "user", content: userMessage });
|
|
3718
|
-
const executionHooks = hookEngine?.createToolExecutionHooks();
|
|
3719
|
-
const onStop = hookEngine ? async (lastMessage) => {
|
|
3720
|
-
const result = await hookEngine.fire("Stop", {
|
|
3721
|
-
stop_hook_active: true,
|
|
3722
|
-
last_assistant_message: lastMessage
|
|
3723
|
-
});
|
|
3724
|
-
return {
|
|
3725
|
-
blocked: result.blocked,
|
|
3726
|
-
reason: result.reason
|
|
3727
|
-
};
|
|
3728
|
-
} : void 0;
|
|
3729
3115
|
return runAgentLoop(
|
|
3730
3116
|
session.messages,
|
|
3731
3117
|
{
|
|
@@ -3733,8 +3119,6 @@ async function sendMessage(session, userMessage, callbacks, options) {
|
|
|
3733
3119
|
system: session.agent.systemPrompt,
|
|
3734
3120
|
registry: session.agent.registry,
|
|
3735
3121
|
pipeline: session.agent.pipeline,
|
|
3736
|
-
executionHooks,
|
|
3737
|
-
onStop,
|
|
3738
3122
|
abortSignal: options?.abortSignal,
|
|
3739
3123
|
maxTurns: options?.maxTurns ?? session.agent.maxTurns,
|
|
3740
3124
|
maxRetries: options?.maxRetries,
|
|
@@ -3747,16 +3131,16 @@ async function sendMessage(session, userMessage, callbacks, options) {
|
|
|
3747
3131
|
}
|
|
3748
3132
|
|
|
3749
3133
|
// src/core/history.ts
|
|
3750
|
-
import { appendFile as appendFile3, readFile as
|
|
3751
|
-
import { join as
|
|
3752
|
-
import { homedir as
|
|
3134
|
+
import { appendFile as appendFile3, readFile as readFile9, mkdir as mkdir6, readdir as readdir4 } from "fs/promises";
|
|
3135
|
+
import { join as join10 } from "path";
|
|
3136
|
+
import { homedir as homedir9 } from "os";
|
|
3753
3137
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3754
|
-
var WELLGROW_DIR =
|
|
3755
|
-
var HISTORY_FILE =
|
|
3138
|
+
var WELLGROW_DIR = join10(homedir9(), ".wellgrow");
|
|
3139
|
+
var HISTORY_FILE = join10(WELLGROW_DIR, "history.jsonl");
|
|
3756
3140
|
async function createSessionRecorder(model, agent) {
|
|
3757
3141
|
const sessionId = randomUUID3();
|
|
3758
3142
|
const now = /* @__PURE__ */ new Date();
|
|
3759
|
-
const dateDir =
|
|
3143
|
+
const dateDir = join10(
|
|
3760
3144
|
WELLGROW_DIR,
|
|
3761
3145
|
"sessions",
|
|
3762
3146
|
String(now.getFullYear()),
|
|
@@ -3764,7 +3148,7 @@ async function createSessionRecorder(model, agent) {
|
|
|
3764
3148
|
String(now.getDate()).padStart(2, "0")
|
|
3765
3149
|
);
|
|
3766
3150
|
await mkdir6(dateDir, { recursive: true });
|
|
3767
|
-
const sessionFile =
|
|
3151
|
+
const sessionFile = join10(dateDir, `${sessionId}.jsonl`);
|
|
3768
3152
|
let firstUserMessage = "";
|
|
3769
3153
|
const meta = JSON.stringify({
|
|
3770
3154
|
type: "meta",
|
|
@@ -3810,7 +3194,7 @@ async function createSessionRecorder(model, agent) {
|
|
|
3810
3194
|
}
|
|
3811
3195
|
async function listHistory(limit = 20) {
|
|
3812
3196
|
try {
|
|
3813
|
-
const content = await
|
|
3197
|
+
const content = await readFile9(HISTORY_FILE, "utf-8");
|
|
3814
3198
|
const lines = content.trim().split("\n").filter(Boolean);
|
|
3815
3199
|
return lines.map((line) => JSON.parse(line)).reverse().slice(0, limit);
|
|
3816
3200
|
} catch {
|
|
@@ -3818,18 +3202,18 @@ async function listHistory(limit = 20) {
|
|
|
3818
3202
|
}
|
|
3819
3203
|
}
|
|
3820
3204
|
async function getSessionContent(sessionId) {
|
|
3821
|
-
const sessionsDir =
|
|
3205
|
+
const sessionsDir = join10(WELLGROW_DIR, "sessions");
|
|
3822
3206
|
try {
|
|
3823
|
-
const years = await
|
|
3207
|
+
const years = await readdir4(sessionsDir);
|
|
3824
3208
|
for (const year of years) {
|
|
3825
|
-
const months = await
|
|
3209
|
+
const months = await readdir4(join10(sessionsDir, year));
|
|
3826
3210
|
for (const month of months) {
|
|
3827
|
-
const days = await
|
|
3211
|
+
const days = await readdir4(join10(sessionsDir, year, month));
|
|
3828
3212
|
for (const day of days) {
|
|
3829
|
-
const files = await
|
|
3213
|
+
const files = await readdir4(join10(sessionsDir, year, month, day));
|
|
3830
3214
|
const match = files.find((f) => f.startsWith(sessionId));
|
|
3831
3215
|
if (match) {
|
|
3832
|
-
return
|
|
3216
|
+
return readFile9(join10(sessionsDir, year, month, day, match), "utf-8");
|
|
3833
3217
|
}
|
|
3834
3218
|
}
|
|
3835
3219
|
}
|
|
@@ -4522,7 +3906,7 @@ function useChatSession({
|
|
|
4522
3906
|
setCurrentAgentName(s.agent.name);
|
|
4523
3907
|
setCurrentAgentIcon(s.agent.icon);
|
|
4524
3908
|
setAgents(agentList);
|
|
4525
|
-
setActiveSession(s.ctx
|
|
3909
|
+
setActiveSession(s.ctx);
|
|
4526
3910
|
registerShutdownHandler(async () => {
|
|
4527
3911
|
if (recorderRef.current) {
|
|
4528
3912
|
await recorderRef.current.finalize(messageCountRef.current);
|
|
@@ -4580,7 +3964,7 @@ function useChatSession({
|
|
|
4580
3964
|
setCurrentModelName(getModelDisplayName(session.agent.modelId));
|
|
4581
3965
|
setCurrentAgentName(session.agent.name);
|
|
4582
3966
|
setCurrentAgentIcon(session.agent.icon);
|
|
4583
|
-
setActiveSession(session.ctx
|
|
3967
|
+
setActiveSession(session.ctx);
|
|
4584
3968
|
recorderRef.current = await createSessionRecorder(
|
|
4585
3969
|
getModelDisplayName(session.agent.modelId),
|
|
4586
3970
|
newAgentName
|
|
@@ -4602,9 +3986,6 @@ function useChatSession({
|
|
|
4602
3986
|
const resetSession = useCallback2(async () => {
|
|
4603
3987
|
const session = sessionRef.current;
|
|
4604
3988
|
if (!session) return;
|
|
4605
|
-
if (session.agent.hookEngine) {
|
|
4606
|
-
await session.agent.hookEngine.fire("SessionEnd", { reason: "clear" });
|
|
4607
|
-
}
|
|
4608
3989
|
if (recorderRef.current) {
|
|
4609
3990
|
await recorderRef.current.finalize(messageCountRef.current);
|
|
4610
3991
|
}
|
|
@@ -4612,8 +3993,7 @@ function useChatSession({
|
|
|
4612
3993
|
const newSession = await createSession({
|
|
4613
3994
|
agentName: currentAgentId,
|
|
4614
3995
|
modelOverride,
|
|
4615
|
-
modeOverride: mode
|
|
4616
|
-
sessionStartSource: "clear"
|
|
3996
|
+
modeOverride: mode
|
|
4617
3997
|
});
|
|
4618
3998
|
if (verbose) {
|
|
4619
3999
|
newSession.ctx.logFile = await initLogger(
|
|
@@ -4626,7 +4006,7 @@ function useChatSession({
|
|
|
4626
4006
|
setCurrentModelName(getModelDisplayName(newSession.agent.modelId));
|
|
4627
4007
|
setCurrentAgentName(newSession.agent.name);
|
|
4628
4008
|
setCurrentAgentIcon(newSession.agent.icon);
|
|
4629
|
-
setActiveSession(newSession.ctx
|
|
4009
|
+
setActiveSession(newSession.ctx);
|
|
4630
4010
|
recorderRef.current = await createSessionRecorder(
|
|
4631
4011
|
getModelDisplayName(newSession.agent.modelId),
|
|
4632
4012
|
currentAgentId
|
|
@@ -4815,10 +4195,6 @@ function useAskUserQueue(getAskUserState) {
|
|
|
4815
4195
|
async function handleSlashCommand(text, ctx) {
|
|
4816
4196
|
if (text === "exit" || text === "quit") {
|
|
4817
4197
|
ctx.cancelAllAskUser();
|
|
4818
|
-
if (ctx.session.agent.hookEngine) {
|
|
4819
|
-
await ctx.session.agent.hookEngine.fire("SessionEnd", { reason: "prompt_input_exit" }).catch(() => {
|
|
4820
|
-
});
|
|
4821
|
-
}
|
|
4822
4198
|
if (ctx.recorder) {
|
|
4823
4199
|
await ctx.recorder.finalize(ctx.messageCountRef.current);
|
|
4824
4200
|
}
|
|
@@ -5413,22 +4789,22 @@ function registerDoctorCommand(program2) {
|
|
|
5413
4789
|
|
|
5414
4790
|
// src/commands/skills.ts
|
|
5415
4791
|
import {
|
|
5416
|
-
readFile as
|
|
5417
|
-
readdir as
|
|
4792
|
+
readFile as readFile10,
|
|
4793
|
+
readdir as readdir5,
|
|
5418
4794
|
mkdir as mkdir7,
|
|
5419
4795
|
cp,
|
|
5420
4796
|
rm as rm2,
|
|
5421
4797
|
mkdtemp,
|
|
5422
|
-
stat as
|
|
4798
|
+
stat as stat3
|
|
5423
4799
|
} from "fs/promises";
|
|
5424
|
-
import { join as
|
|
5425
|
-
import { homedir as
|
|
4800
|
+
import { join as join11, resolve as resolve4 } from "path";
|
|
4801
|
+
import { homedir as homedir10, tmpdir } from "os";
|
|
5426
4802
|
import { execFile as execFile4 } from "child_process";
|
|
5427
4803
|
import { promisify as promisify4 } from "util";
|
|
5428
4804
|
import matter2 from "gray-matter";
|
|
5429
4805
|
var execFileAsync4 = promisify4(execFile4);
|
|
5430
|
-
var WELLGROW_HOME2 =
|
|
5431
|
-
var SKILLS_DIR =
|
|
4806
|
+
var WELLGROW_HOME2 = join11(homedir10(), ".wellgrow");
|
|
4807
|
+
var SKILLS_DIR = join11(WELLGROW_HOME2, "skills");
|
|
5432
4808
|
var SKIP_DIRS = /* @__PURE__ */ new Set([".git", "node_modules", ".github", ".husky"]);
|
|
5433
4809
|
function parseSource(source) {
|
|
5434
4810
|
if (source.startsWith("./") || source.startsWith("../")) {
|
|
@@ -5457,7 +4833,7 @@ async function discoverSkillsInRepo(dir) {
|
|
|
5457
4833
|
async function scan(currentDir, depth) {
|
|
5458
4834
|
if (depth > 3) return;
|
|
5459
4835
|
try {
|
|
5460
|
-
const content = await
|
|
4836
|
+
const content = await readFile10(join11(currentDir, "SKILL.md"), "utf-8");
|
|
5461
4837
|
const { data } = matter2(content);
|
|
5462
4838
|
if (data.name && data.description && !seen.has(data.name)) {
|
|
5463
4839
|
seen.add(data.name);
|
|
@@ -5471,10 +4847,10 @@ async function discoverSkillsInRepo(dir) {
|
|
|
5471
4847
|
} catch {
|
|
5472
4848
|
}
|
|
5473
4849
|
try {
|
|
5474
|
-
const entries = await
|
|
4850
|
+
const entries = await readdir5(currentDir, { withFileTypes: true });
|
|
5475
4851
|
for (const entry of entries) {
|
|
5476
4852
|
if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) continue;
|
|
5477
|
-
await scan(
|
|
4853
|
+
await scan(join11(currentDir, entry.name), depth + 1);
|
|
5478
4854
|
}
|
|
5479
4855
|
} catch {
|
|
5480
4856
|
}
|
|
@@ -5487,7 +4863,7 @@ async function fetchSource(source) {
|
|
|
5487
4863
|
if (!parsed.url.startsWith("https://") && !parsed.url.startsWith("git@")) {
|
|
5488
4864
|
return { dir: parsed.url, isTemp: false };
|
|
5489
4865
|
}
|
|
5490
|
-
const tmpDir = await mkdtemp(
|
|
4866
|
+
const tmpDir = await mkdtemp(join11(tmpdir(), "wellgrow-skills-"));
|
|
5491
4867
|
const args = ["clone", "--depth", "1"];
|
|
5492
4868
|
if (parsed.ref) {
|
|
5493
4869
|
args.push("--branch", parsed.ref);
|
|
@@ -5559,7 +4935,7 @@ ${toInstall.length} \u500B\u306E\u30B9\u30AD\u30EB\u3092\u30A4\u30F3\u30B9\u30C8
|
|
|
5559
4935
|
`
|
|
5560
4936
|
);
|
|
5561
4937
|
for (const skill of toInstall) {
|
|
5562
|
-
const dest =
|
|
4938
|
+
const dest = join11(SKILLS_DIR, skill.name);
|
|
5563
4939
|
await cp(skill.sourceDir, dest, { recursive: true, force: true });
|
|
5564
4940
|
process.stdout.write(` \u2713 ${skill.name}
|
|
5565
4941
|
`);
|
|
@@ -5587,7 +4963,7 @@ ${toInstall.length} \u500B\u306E\u30B9\u30AD\u30EB\u3092\u30A4\u30F3\u30B9\u30C8
|
|
|
5587
4963
|
skills.command("list").alias("ls").description("\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u6E08\u307F\u30B9\u30AD\u30EB\u3092\u4E00\u89A7\u8868\u793A").action(async () => {
|
|
5588
4964
|
let dirs;
|
|
5589
4965
|
try {
|
|
5590
|
-
const entries = await
|
|
4966
|
+
const entries = await readdir5(SKILLS_DIR, { withFileTypes: true });
|
|
5591
4967
|
dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
5592
4968
|
} catch {
|
|
5593
4969
|
dirs = [];
|
|
@@ -5602,8 +4978,8 @@ ${dirs.length} \u500B\u306E\u30B9\u30AD\u30EB (${SKILLS_DIR}):
|
|
|
5602
4978
|
`);
|
|
5603
4979
|
for (const name of dirs) {
|
|
5604
4980
|
try {
|
|
5605
|
-
const content = await
|
|
5606
|
-
|
|
4981
|
+
const content = await readFile10(
|
|
4982
|
+
join11(SKILLS_DIR, name, "SKILL.md"),
|
|
5607
4983
|
"utf-8"
|
|
5608
4984
|
);
|
|
5609
4985
|
const { data } = matter2(content);
|
|
@@ -5622,9 +4998,9 @@ ${dirs.length} \u500B\u306E\u30B9\u30AD\u30EB (${SKILLS_DIR}):
|
|
|
5622
4998
|
}
|
|
5623
4999
|
});
|
|
5624
5000
|
skills.command("remove <name>").alias("rm").description("\u30B9\u30AD\u30EB\u3092\u524A\u9664").action(async (name) => {
|
|
5625
|
-
const skillDir =
|
|
5001
|
+
const skillDir = join11(SKILLS_DIR, name);
|
|
5626
5002
|
try {
|
|
5627
|
-
await
|
|
5003
|
+
await stat3(skillDir);
|
|
5628
5004
|
} catch {
|
|
5629
5005
|
process.stderr.write(`\u30B9\u30AD\u30EB "${name}" \u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\u3002
|
|
5630
5006
|
`);
|
|
@@ -5969,7 +5345,7 @@ Error: ${formatErrorMessage(error)}
|
|
|
5969
5345
|
}
|
|
5970
5346
|
}
|
|
5971
5347
|
var program = new Command();
|
|
5972
|
-
program.name("wellgrow").description("WellGrow CLI \u2014 AI chat assistant").version("0.
|
|
5348
|
+
program.name("wellgrow").description("WellGrow CLI \u2014 AI chat assistant").version("0.2.0").argument("[prompt]", "\u30EF\u30F3\u30B7\u30E7\u30C3\u30C8\u8CEA\u554F\uFF08\u7701\u7565\u3067\u30A4\u30F3\u30BF\u30E9\u30AF\u30C6\u30A3\u30D6\u30E2\u30FC\u30C9\uFF09").option("--model <model>", "\u4F7F\u7528\u3059\u308B\u30E2\u30C7\u30EB").option("-a, --agent <agent>", "\u4F7F\u7528\u3059\u308B\u30A8\u30FC\u30B8\u30A7\u30F3\u30C8").option(
|
|
5973
5349
|
"--mode <mode>",
|
|
5974
5350
|
"\u30E2\u30FC\u30C9 (plan, auto)"
|
|
5975
5351
|
).option("--verbose", "\u8A73\u7D30\u30ED\u30B0\u3092\u51FA\u529B").option("-p, --pipe", "\u30D1\u30A4\u30D7\u5165\u529B\u30E2\u30FC\u30C9\uFF08stdin\u304B\u3089\u306E\u5165\u529B\u3092\u53D7\u3051\u4ED8\u3051\u308B\uFF09").action(async (prompt, opts) => {
|