chainlesschain 0.47.0 → 0.47.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -198,7 +198,7 @@ chainlesschain a --model llama3 # Short alias
198
198
  chainlesschain agent --provider openai --api-key sk-...
199
199
  ```
200
200
 
201
- Built-in tools (12): `read_file`, `write_file`, `edit_file`, `edit_file_hashed`, `run_shell`, `git`, `search_files`, `list_dir`, `run_skill`, `list_skills`, `run_code`, `spawn_sub_agent`
201
+ Built-in tools (16): `read_file`, `write_file`, `edit_file`, `edit_file_hashed`, `run_shell`, `git`, `search_files`, `search_sessions`, `list_dir`, `run_skill`, `list_skills`, `run_code`, `spawn_sub_agent`, `web_fetch`, `todo_write`, `ask_user_question`
202
202
 
203
203
  Agent slash commands: `/plan` (plan mode), `/plan interactive <request>` (LLM-driven planning with skill recommendations), `/model`, `/provider`, `/clear`, `/compact`, `/task`, `/session`, `/stats`, `/auto` (autonomous agent), `/cowork` (multi-agent collaboration), `/sub-agents` (show active/completed sub-agents)
204
204
 
@@ -623,11 +623,11 @@ chainlesschain cowork analyze <path> # Code analysis (style/kn
623
623
  chainlesschain cowork status # Show cowork status
624
624
  ```
625
625
 
626
- ### Web Cowork: Daily Task Collaboration (v0.45.77)
626
+ ### Web Cowork: Daily Task Collaboration (v0.46.0)
627
627
 
628
- Web-based daily task collaboration via the `/#/cowork` page. Powered by SubAgentContext + agentLoop, with 10 task templates covering common daily tasks. Prioritizes open-source CLI tools (ffmpeg, pandoc, ImageMagick, Tesseract, etc.) via cli-anything bridge, with automatic tool installation (winget > choco > pip > npm).
628
+ Web-based daily task collaboration via the `/#/cowork` page. Powered by SubAgentContext + agentLoop, with 11 task templates covering common daily tasks. Prioritizes open-source CLI tools (ffmpeg, pandoc, ImageMagick, Tesseract, etc.) via cli-anything bridge, with automatic tool installation (winget > choco > pip > npm).
629
629
 
630
- **10 Task Templates**:
630
+ **11 Task Templates**:
631
631
 
632
632
  | Template ID | Name | Category | Accepts Files |
633
633
  | ----------------- | ------------ | --------- | ------------- |
@@ -637,6 +637,7 @@ Web-based daily task collaboration via the `/#/cowork` page. Powered by SubAgent
637
637
  | `web-research` | 网络调研 | research | ❌ |
638
638
  | `image-process` | 图片处理 | image | ✅ |
639
639
  | `code-helper` | 代码辅助 | code | ✅ |
640
+ | `code_review` | 代码评审 | code | ✅ |
640
641
  | `system-admin` | 系统管理 | system | ❌ |
641
642
  | `file-organize` | 文件整理 | file | ✅ |
642
643
  | `network-tools` | 网络工具 | network | ❌ |
@@ -652,7 +653,7 @@ Web-based daily task collaboration via the `/#/cowork` page. Powered by SubAgent
652
653
  ← { type: "cowork:done", taskId, status, templateName, summary, artifacts, toolsUsed, iterationCount }
653
654
  ```
654
655
 
655
- **Key files**: `src/lib/cowork-task-templates.js` (10 templates), `src/lib/cowork-task-runner.js` (pipeline), `src/gateways/ws/action-protocol.js` (WS handler). **Tests**: 79 (57 unit + 11 integration + 11 E2E).
656
+ **Key files**: `src/lib/cowork-task-templates.js` (11 templates), `src/lib/cowork-task-runner.js` (pipeline), `src/gateways/ws/action-protocol.js` (WS handler). **Tests**: 79+ (57 unit + 11 integration + 11 E2E).
656
657
 
657
658
  ### Cowork Workflow Editor (v0.47.0, N1)
658
659
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chainlesschain",
3
- "version": "0.47.0",
3
+ "version": "0.47.2",
4
4
  "description": "CLI for ChainlessChain - install, configure, and manage your personal AI management system",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,19 +1,19 @@
1
1
  /**
2
- * Session-level hook firing helpers — the "三件套" that complements
2
+ * Session-level hook firing helpers — the "三件套 +1" that complements
3
3
  * the tool-level PreToolUse/PostToolUse hooks already wired in
4
4
  * `runtime/agent-core.js`.
5
5
  *
6
- * These three events are defined in hook-manager.js but were never
7
- * actually fired anywhere in the CLI. This module is the canonical
8
- * fire site, consumed by `repl/agent-repl.js`.
6
+ * Four session-level events are fired from `repl/agent-repl.js`:
9
7
  *
10
- * - SessionStart — once, after sessionId is established
11
- * - UserPromptSubmit — per user line, before agentLoop()
12
- * - SessionEnd once, on rl.close() before shutdown
8
+ * - SessionStart — once, after sessionId is established
9
+ * - UserPromptSubmit — per user line, before agentLoop()
10
+ * - AssistantResponse per agent reply, after agentLoop() returns
11
+ * - SessionEnd — once, on rl.close() before shutdown
13
12
  *
14
13
  * Semantics (matches existing PreToolUse convention):
15
- * - Fire-and-forget: hook failures NEVER break the host flow
16
- * - Observational only: no abort, no rewrite (can be added later)
14
+ * - Fire-and-forget by default: hook failures NEVER break the host flow
15
+ * - `fireSessionHookWithRewrite` opt-in: lets a UserPromptSubmit hook
16
+ * return `{ rewrittenPrompt }` or `{ abort: true }` via stdout JSON
17
17
  * - No-op when hookDb is null (REPL without DB)
18
18
  */
19
19
 
@@ -26,6 +26,7 @@ import { executeHooks, HookEvents } from "./hook-manager.js";
26
26
  export const SESSION_HOOK_EVENTS = Object.freeze([
27
27
  HookEvents.SessionStart,
28
28
  HookEvents.UserPromptSubmit,
29
+ HookEvents.AssistantResponse,
29
30
  HookEvents.SessionEnd,
30
31
  ]);
31
32
 
@@ -59,3 +60,64 @@ export async function fireSessionHook(hookDb, eventName, context = {}) {
59
60
  return [];
60
61
  }
61
62
  }
63
+
64
+ /**
65
+ * Fire UserPromptSubmit with rewrite/abort support.
66
+ *
67
+ * A hook may control the prompt by emitting a single JSON line to stdout:
68
+ * {"rewrittenPrompt": "..."} — replace the user prompt
69
+ * {"abort": true, "reason": "..."} — skip agentLoop entirely
70
+ *
71
+ * First matching directive wins (priority order from executeHooks).
72
+ * Malformed JSON / no directive → no change (pure observation).
73
+ *
74
+ * @returns {Promise<{prompt: string, abort: boolean, reason?: string, results: Array}>}
75
+ */
76
+ export async function fireUserPromptSubmit(
77
+ hookDb,
78
+ originalPrompt,
79
+ context = {},
80
+ ) {
81
+ const results = await fireSessionHook(hookDb, HookEvents.UserPromptSubmit, {
82
+ ...context,
83
+ prompt: originalPrompt,
84
+ });
85
+
86
+ let prompt = originalPrompt;
87
+ let abort = false;
88
+ let reason;
89
+
90
+ for (const r of results) {
91
+ if (!r || !r.success) continue;
92
+ const directive = extractDirective(r);
93
+ if (!directive) continue;
94
+ if (directive.abort) {
95
+ abort = true;
96
+ reason = directive.reason;
97
+ break;
98
+ }
99
+ if (
100
+ typeof directive.rewrittenPrompt === "string" &&
101
+ directive.rewrittenPrompt.trim()
102
+ ) {
103
+ prompt = directive.rewrittenPrompt;
104
+ break;
105
+ }
106
+ }
107
+
108
+ return { prompt, abort, reason, results };
109
+ }
110
+
111
+ function extractDirective(result) {
112
+ const raw = result.stdout ?? result.output ?? result.result;
113
+ if (raw == null) return null;
114
+ if (typeof raw === "object") return raw;
115
+ if (typeof raw !== "string") return null;
116
+ const trimmed = raw.trim();
117
+ if (!trimmed.startsWith("{")) return null;
118
+ try {
119
+ return JSON.parse(trimmed);
120
+ } catch {
121
+ return null;
122
+ }
123
+ }
@@ -50,7 +50,7 @@ import { CLIAutonomousAgent, GoalStatus } from "../lib/autonomous-agent.js";
50
50
  import { PromptCompressor } from "../harness/prompt-compressor.js";
51
51
  import { feature } from "../lib/feature-flags.js";
52
52
  import { recordCompressionMetric } from "../lib/compression-telemetry.js";
53
- import { fireSessionHook } from "../lib/session-hooks.js";
53
+ import { fireSessionHook, fireUserPromptSubmit } from "../lib/session-hooks.js";
54
54
  import { HookEvents } from "../lib/hook-manager.js";
55
55
  import { IterationBudget } from "../lib/iteration-budget.js";
56
56
  import {
@@ -1161,15 +1161,29 @@ export async function startAgentRepl(options = {}) {
1161
1161
  return;
1162
1162
  }
1163
1163
 
1164
- // Fire UserPromptSubmit hook (fire-and-forget; observational only)
1165
- await fireSessionHook(_hookDb, HookEvents.UserPromptSubmit, {
1164
+ // Fire UserPromptSubmit hook with rewrite/abort support.
1165
+ // Hooks may emit {"rewrittenPrompt": "..."} or {"abort": true, "reason": "..."}
1166
+ // via stdout JSON. Failures fall through to the original prompt.
1167
+ const promptDirective = await fireUserPromptSubmit(_hookDb, trimmed, {
1166
1168
  sessionId,
1167
- prompt: trimmed,
1168
1169
  messageCount: messages.length,
1169
1170
  });
1171
+ if (promptDirective.abort) {
1172
+ logger.info(
1173
+ chalk.yellow(
1174
+ `[hook] prompt aborted${promptDirective.reason ? `: ${promptDirective.reason}` : ""}`,
1175
+ ),
1176
+ );
1177
+ prompt();
1178
+ return;
1179
+ }
1180
+ const effectivePrompt = promptDirective.prompt;
1181
+ if (effectivePrompt !== trimmed) {
1182
+ logger.verbose(`[hook] prompt rewritten by UserPromptSubmit hook`);
1183
+ }
1170
1184
 
1171
1185
  // Add user message
1172
- messages.push({ role: "user", content: trimmed });
1186
+ messages.push({ role: "user", content: effectivePrompt });
1173
1187
 
1174
1188
  // Slot-filling: detect intent and fill missing parameters interactively
1175
1189
  try {
@@ -1236,12 +1250,21 @@ export async function startAgentRepl(options = {}) {
1236
1250
  process.stdout.write("\n");
1237
1251
  }
1238
1252
 
1253
+ // Fire AssistantResponse hook (fire-and-forget; observational only)
1254
+ await fireSessionHook(_hookDb, HookEvents.AssistantResponse, {
1255
+ sessionId,
1256
+ response: response || "",
1257
+ messageCount: messages.length,
1258
+ provider,
1259
+ model: activeModel,
1260
+ });
1261
+
1239
1262
  // Auto-save session
1240
1263
  if (sessionId) {
1241
1264
  try {
1242
1265
  if (useJsonl) {
1243
1266
  // Append incremental events (user + assistant)
1244
- appendUserMessage(sessionId, trimmed);
1267
+ appendUserMessage(sessionId, effectivePrompt);
1245
1268
  if (response) {
1246
1269
  appendAssistantMessage(sessionId, response);
1247
1270
  }