@opencoven/coven-code 0.0.2 → 0.0.4

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
@@ -34,7 +34,7 @@ filtering and a details panel for commands, skills, and plugin actions.
34
34
 
35
35
  ```text
36
36
  $ coven-code
37
- Coven Code 0.0.2
37
+ Coven Code 0.0.4
38
38
  /Users/example/project
39
39
  [chat] lane tools threads config help mode: smart effort: high
40
40
  --------------------------------------------------------------------------------
package/docs/CLI.md CHANGED
@@ -99,7 +99,7 @@ transcript, tabs, compact status line, command palette, slash-command menu, and
99
99
  composer. It keeps a current local thread and accepts slash commands:
100
100
 
101
101
  ```text
102
- Coven Code 0.0.2
102
+ Coven Code 0.0.4
103
103
  [chat] lane tools threads config help mode: smart effort: high
104
104
  --------------------------------------------------------------------------------
105
105
  Ready. Type a prompt or /help.
package/docs/DEMO.md CHANGED
@@ -90,8 +90,11 @@ The menu accepts:
90
90
  | -------- | -------------------------------------------------------- |
91
91
  | `1`-`12` | run one section by number |
92
92
  | `a` | run all sections in sequence, then print the scoreboard |
93
+ | `r` | replay the last section you ran |
93
94
  | `t` | type your own prompt and run one execute turn |
94
95
  | `s` | list the files the demo wrote into the sandbox HOME |
96
+ | `x` | open a real subshell inside the sandbox (`exit` returns) |
97
+ | `c` | copy the sandbox path to your clipboard |
95
98
  | `l` | re-print the section table of contents (with `✓` marks) |
96
99
  | `?` | show the menu help |
97
100
  | `q` | quit (sandbox path printed so you can clean up or poke around) |
@@ -144,7 +147,7 @@ rm -rf "$DEMO_HOME" # the script prints the exact path at the end
144
147
  works without any account or API key.
145
148
 
146
149
  ```sh
147
- node ./bin/coven-code.mjs --version # prints 0.0.2
150
+ node ./bin/coven-code.mjs --version # prints 0.0.4
148
151
  node ./bin/coven-code.mjs --help # full option and command reference
149
152
  node ./bin/coven-code.mjs login # printable instructions (no token yet)
150
153
  node ./bin/coven-code.mjs login status # auth_status: logged_out
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opencoven/coven-code",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,33 +1,24 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import path from 'node:path';
4
- import { BUILTIN_TOOLS } from '../constants.mjs';
5
4
  import { estimateUsage, fixtureAgentResponse } from '../agent/fixture.mjs';
6
- import {
7
- listActiveMcpServerEntries,
8
- } from '../mcp/discover.mjs';
9
- import { discoverMcpToolRows } from '../mcp/probe.mjs';
10
5
  import {
11
6
  loadPlugins,
12
7
  runPluginEventHandlers,
13
8
  } from '../plugins/discover.mjs';
14
9
  import { discoverAgentFiles, firstGuidanceInDir } from '../commands/agents.mjs';
15
- import {
16
- isToolDisabled,
17
- listToolboxTools,
18
- toolKindForName,
19
- } from '../tools/toolbox.mjs';
20
10
  import { executePromptToolRequest } from '../tools/builtin/index.mjs';
21
11
  import { toolResultContent } from '../tools/builtin/runtime.mjs';
22
12
  import { persistThreadMessages, threadContinuationPrompt } from '../threads/store.mjs';
23
13
  import { readEffectiveSettings } from '../settings/load.mjs';
24
14
  import { expandFileReferences, expandThreadReferences } from './refs.mjs';
25
15
  import { UsageError } from './parse.mjs';
26
- import { reasoningEffortForMode } from './reasoning.mjs';
27
16
  import { notifyAgentComplete } from './notifications.mjs';
28
17
  import { globToRegex } from '../util/glob.mjs';
29
- import { displayCwd, emitJson } from '../util/fs.mjs';
18
+ import { emitJson } from '../util/fs.mjs';
30
19
  import {
20
+ emitStreamJsonInit,
21
+ emitStreamJsonTurn,
31
22
  streamJsonInputMessages,
32
23
  streamJsonOutputUserContent,
33
24
  streamJsonPermissionDenials,
@@ -60,83 +51,21 @@ export async function runExecute(parsed, stdin, options = {}) {
60
51
  const result = turns.at(-1)?.result ?? '';
61
52
 
62
53
  if (parsed.streamJson) {
63
- const activeMcpServers = listActiveMcpServerEntries(parsed, turns.map((turn) => turn.prompt).join('\n\n'));
64
- const tools = [
65
- ...BUILTIN_TOOLS.map(([name]) => name),
66
- ...listToolboxTools(parsed).map((tool) => tool.name),
67
- ...plugins.tools.map((tool) => tool.name),
68
- ...(await discoverMcpToolRows(activeMcpServers)).map(([name]) => name),
69
- ].filter((name) => !isToolDisabled(name, toolKindForName(name), parsed));
70
- emitJson({
71
- type: 'system',
72
- subtype: 'init',
73
- cwd: displayCwd(),
74
- session_id: sessionId,
75
- tools,
76
- mcp_servers: activeMcpServers.map(({ name }) => ({ name, status: 'connected' })),
77
- agent_mode: parsed.mode,
78
- reasoning_effort: reasoningEffortForMode(parsed.mode, parsed.reasoningEffort),
54
+ await emitStreamJsonInit({
55
+ parsed,
56
+ plugins,
57
+ sessionId,
58
+ promptForMcpDiscovery: turns.map((turn) => turn.prompt).join('\n\n'),
79
59
  });
80
60
  for (const turn of turns) {
81
- emitJson({
82
- type: 'user',
83
- message: { role: 'user', content: [{ type: 'text', text: turn.prompt }] },
84
- parent_tool_use_id: null,
85
- session_id: sessionId,
86
- });
87
- if (turn.toolRun?.toolUse) {
88
- emitJson({
89
- type: 'assistant',
90
- message: {
91
- type: 'message',
92
- role: 'assistant',
93
- content: [turn.toolRun.toolUse],
94
- stop_reason: 'tool_use',
95
- usage: estimateUsage(turn.prompt, JSON.stringify(turn.toolRun.toolUse.input)),
96
- },
97
- parent_tool_use_id: null,
98
- session_id: sessionId,
99
- });
100
- for (const subagentMessage of turn.toolRun.subagentMessages ?? []) {
101
- emitJson({
102
- type: 'assistant',
103
- message: {
104
- type: 'message',
105
- role: 'assistant',
106
- content: assistantStreamContent(subagentMessage.text ?? '', parsed),
107
- stop_reason: 'end_turn',
108
- usage: estimateUsage(turn.prompt, subagentMessage.text ?? ''),
109
- },
110
- parent_tool_use_id: turn.toolRun.toolUse.id,
111
- session_id: sessionId,
112
- });
113
- }
114
- emitJson({
115
- type: 'user',
116
- message: {
117
- role: 'user',
118
- content: [{
119
- type: 'tool_result',
120
- tool_use_id: turn.toolRun.toolUse.id,
121
- content: toolResultContent(turn.toolRun),
122
- is_error: turn.toolRun.exitCode !== 0,
123
- }],
124
- },
125
- parent_tool_use_id: toolRunParent(turn.toolRun, 'toolResultParentToolUseId', null),
126
- session_id: sessionId,
127
- });
128
- }
129
- emitJson({
130
- type: 'assistant',
131
- message: {
132
- type: 'message',
133
- role: 'assistant',
134
- content: assistantStreamContent(turn.result, parsed),
135
- stop_reason: 'end_turn',
136
- usage: estimateUsage(turn.prompt, turn.result),
137
- },
138
- parent_tool_use_id: turn.toolRun ? toolRunParent(turn.toolRun, 'finalParentToolUseId', null) : null,
139
- session_id: sessionId,
61
+ emitStreamJsonTurn({
62
+ userContent: [{ type: 'text', text: turn.prompt }],
63
+ promptText: turn.prompt,
64
+ toolRun: turn.toolRun,
65
+ result: turn.result,
66
+ parsed,
67
+ sessionId,
68
+ assistantParentToolUseId: turn.toolRun ? toolRunParent(turn.toolRun, 'finalParentToolUseId', null) : null,
140
69
  });
141
70
  }
142
71
  emitJson(streamJsonResultMessage({
@@ -312,22 +241,11 @@ async function runStreamJsonInputExecute(parsed, stdin, options = {}, started =
312
241
  const combinedPrompt = inputMessages.map((message) => message.text).filter(Boolean).join('\n\n');
313
242
  const plugins = await loadPlugins(process.cwd());
314
243
  await runSessionStartHandlers(plugins, sessionId);
315
- const activeMcpServers = listActiveMcpServerEntries(parsed, combinedPrompt);
316
- const tools = [
317
- ...BUILTIN_TOOLS.map(([name]) => name),
318
- ...listToolboxTools(parsed).map((tool) => tool.name),
319
- ...plugins.tools.map((tool) => tool.name),
320
- ...(await discoverMcpToolRows(activeMcpServers)).map(([name]) => name),
321
- ].filter((name) => !isToolDisabled(name, toolKindForName(name), parsed));
322
- emitJson({
323
- type: 'system',
324
- subtype: 'init',
325
- cwd: displayCwd(),
326
- session_id: sessionId,
327
- tools,
328
- mcp_servers: activeMcpServers.map(({ name }) => ({ name, status: 'connected' })),
329
- agent_mode: parsed.mode,
330
- reasoning_effort: reasoningEffortForMode(parsed.mode, parsed.reasoningEffort),
244
+ await emitStreamJsonInit({
245
+ parsed,
246
+ plugins,
247
+ sessionId,
248
+ promptForMcpDiscovery: combinedPrompt,
331
249
  });
332
250
 
333
251
  const transcript = [];
@@ -356,67 +274,18 @@ async function runStreamJsonInputExecute(parsed, stdin, options = {}, started =
356
274
  if (toolRun?.permissionDenials) permissionDenials.push(...toolRun.permissionDenials);
357
275
  if ((toolRun?.exitCode ?? 0) !== 0) isError = true;
358
276
 
359
- emitJson({
360
- type: 'user',
361
- ...(input.steer ? { steer: true } : {}),
362
- message: { role: 'user', content: streamJsonOutputUserContent(input.content) },
363
- parent_tool_use_id: null,
364
- session_id: sessionId,
365
- });
366
277
  if (toolRun?.toolUse) {
367
- emitJson({
368
- type: 'assistant',
369
- message: {
370
- type: 'message',
371
- role: 'assistant',
372
- content: [toolRun.toolUse],
373
- stop_reason: 'tool_use',
374
- usage: estimateUsage(input.text, JSON.stringify(toolRun.toolUse.input)),
375
- },
376
- parent_tool_use_id: null,
377
- session_id: sessionId,
378
- });
379
- for (const subagentMessage of toolRun.subagentMessages ?? []) {
380
- emitJson({
381
- type: 'assistant',
382
- message: {
383
- type: 'message',
384
- role: 'assistant',
385
- content: assistantStreamContent(subagentMessage.text ?? '', parsed),
386
- stop_reason: 'end_turn',
387
- usage: estimateUsage(input.text, subagentMessage.text ?? ''),
388
- },
389
- parent_tool_use_id: toolRun.toolUse.id,
390
- session_id: sessionId,
391
- });
392
- }
393
- emitJson({
394
- type: 'user',
395
- message: {
396
- role: 'user',
397
- content: [{
398
- type: 'tool_result',
399
- tool_use_id: toolRun.toolUse.id,
400
- content: toolResultContent(toolRun),
401
- is_error: toolRun.exitCode !== 0,
402
- }],
403
- },
404
- parent_tool_use_id: toolRunParent(toolRun, 'toolResultParentToolUseId', null),
405
- session_id: sessionId,
406
- });
407
278
  parentToolUseId = toolRunParent(toolRun, 'finalParentToolUseId', null);
408
279
  }
409
- emitJson({
410
- type: 'assistant',
411
- message: {
412
- type: 'message',
413
- role: 'assistant',
414
- content: assistantStreamContent(result, parsed),
415
- stop_reason: 'end_turn',
416
- usage: estimateUsage(input.text, result),
417
- },
418
- parent_tool_use_id: parentToolUseId,
419
- session_id: sessionId,
280
+ emitStreamJsonTurn({
281
+ userContent: streamJsonOutputUserContent(input.content),
282
+ steer: input.steer,
283
+ promptText: input.text,
284
+ toolRun,
285
+ result,
286
+ parsed,
287
+ sessionId,
288
+ assistantParentToolUseId: parentToolUseId,
420
289
  });
421
290
  await runPluginEventHandlers(plugins.handlers['agent.end'], {
422
291
  message: input.text,
@@ -453,13 +322,6 @@ async function runSessionStartHandlers(plugins, sessionId) {
453
322
  });
454
323
  }
455
324
 
456
- function assistantStreamContent(result, parsed = {}) {
457
- if (parsed.streamJsonThinking && readEffectiveSettings(parsed)['covenCode.thinking.enabled'] !== false) {
458
- return [{ type: 'thinking', thinking: 'Using the local deterministic recreation.' }, { type: 'text', text: result }];
459
- }
460
- return [{ type: 'text', text: result }];
461
- }
462
-
463
325
  function modelPromptWithTranscript(turnPrompt, transcript, thread, parsed = {}) {
464
326
  const context = transcript.length > 0
465
327
  ? `[conversation:${thread?.id ?? 'stream-json-input'}]\n${transcript
@@ -163,8 +163,8 @@ export async function buildSlashCommandCatalog(options = {}) {
163
163
  const cwd = options.cwd ?? process.cwd();
164
164
  const entries = [
165
165
  ...buildStaticSlashCommandCatalog(),
166
- ...skillSlashCommands(parsed, cwd),
167
- ...await pluginSlashCommands(cwd),
166
+ ...safeSkillSlashCommands(parsed, cwd),
167
+ ...await safePluginSlashCommands(cwd),
168
168
  ];
169
169
  return normalizeCatalog(entries);
170
170
  } finally {
@@ -262,6 +262,24 @@ async function pluginSlashCommands(cwd) {
262
262
  }));
263
263
  }
264
264
 
265
+ function safeSkillSlashCommands(parsed, cwd) {
266
+ try {
267
+ return skillSlashCommands(parsed, cwd);
268
+ } catch (error) {
269
+ console.error(`${CLI_NAME}: skill catalog unavailable: ${error?.message ?? error}`);
270
+ return [];
271
+ }
272
+ }
273
+
274
+ async function safePluginSlashCommands(cwd) {
275
+ try {
276
+ return await pluginSlashCommands(cwd);
277
+ } catch (error) {
278
+ console.error(`${CLI_NAME}: plugin catalog unavailable: ${error?.message ?? error}`);
279
+ return [];
280
+ }
281
+ }
282
+
265
283
  function normalizeCatalog(entries) {
266
284
  const seen = new Set();
267
285
  const catalog = [];
@@ -1,6 +1,15 @@
1
1
  import { fileURLToPath } from 'node:url';
2
+ import { BUILTIN_TOOLS } from '../constants.mjs';
3
+ import { estimateUsage } from '../agent/fixture.mjs';
4
+ import { listActiveMcpServerEntries } from '../mcp/discover.mjs';
5
+ import { discoverMcpToolRows } from '../mcp/probe.mjs';
6
+ import { readEffectiveSettings } from '../settings/load.mjs';
7
+ import { isToolDisabled, listToolboxTools, toolKindForName } from '../tools/toolbox.mjs';
8
+ import { toolResultContent } from '../tools/builtin/runtime.mjs';
2
9
  import { detectImageMediaType, imageMediaTypeExtension } from '../util/media.mjs';
10
+ import { displayCwd, emitJson } from '../util/fs.mjs';
3
11
  import { UsageError } from './parse.mjs';
12
+ import { reasoningEffortForMode } from './reasoning.mjs';
4
13
  import { imageMentionBlock } from './refs.mjs';
5
14
 
6
15
  export function streamJsonInputMessages(prompt, stdin) {
@@ -90,6 +99,106 @@ export function streamJsonPermissionDenials(denials = []) {
90
99
  });
91
100
  }
92
101
 
102
+ export function assistantStreamContent(result, parsed = {}) {
103
+ if (parsed.streamJsonThinking && readEffectiveSettings(parsed)['covenCode.thinking.enabled'] !== false) {
104
+ return [{ type: 'thinking', thinking: 'Using the local deterministic recreation.' }, { type: 'text', text: result }];
105
+ }
106
+ return [{ type: 'text', text: result }];
107
+ }
108
+
109
+ export async function emitStreamJsonInit({ parsed, plugins, sessionId, promptForMcpDiscovery }) {
110
+ const activeMcpServers = listActiveMcpServerEntries(parsed, promptForMcpDiscovery);
111
+ const tools = [
112
+ ...BUILTIN_TOOLS.map(([name]) => name),
113
+ ...listToolboxTools(parsed).map((tool) => tool.name),
114
+ ...plugins.tools.map((tool) => tool.name),
115
+ ...(await discoverMcpToolRows(activeMcpServers)).map(([name]) => name),
116
+ ].filter((name) => !isToolDisabled(name, toolKindForName(name), parsed));
117
+ emitJson({
118
+ type: 'system',
119
+ subtype: 'init',
120
+ cwd: displayCwd(),
121
+ session_id: sessionId,
122
+ tools,
123
+ mcp_servers: activeMcpServers.map(({ name }) => ({ name, status: 'connected' })),
124
+ agent_mode: parsed.mode,
125
+ reasoning_effort: reasoningEffortForMode(parsed.mode, parsed.reasoningEffort),
126
+ });
127
+ }
128
+
129
+ export function emitStreamJsonTurn({
130
+ userContent,
131
+ steer = false,
132
+ promptText,
133
+ toolRun,
134
+ result,
135
+ parsed,
136
+ sessionId,
137
+ assistantParentToolUseId,
138
+ }) {
139
+ emitJson({
140
+ type: 'user',
141
+ ...(steer ? { steer: true } : {}),
142
+ message: { role: 'user', content: userContent },
143
+ parent_tool_use_id: null,
144
+ session_id: sessionId,
145
+ });
146
+ if (toolRun?.toolUse) {
147
+ emitJson({
148
+ type: 'assistant',
149
+ message: {
150
+ type: 'message',
151
+ role: 'assistant',
152
+ content: [toolRun.toolUse],
153
+ stop_reason: 'tool_use',
154
+ usage: estimateUsage(promptText, JSON.stringify(toolRun.toolUse.input)),
155
+ },
156
+ parent_tool_use_id: null,
157
+ session_id: sessionId,
158
+ });
159
+ for (const subagentMessage of toolRun.subagentMessages ?? []) {
160
+ emitJson({
161
+ type: 'assistant',
162
+ message: {
163
+ type: 'message',
164
+ role: 'assistant',
165
+ content: assistantStreamContent(subagentMessage.text ?? '', parsed),
166
+ stop_reason: 'end_turn',
167
+ usage: estimateUsage(promptText, subagentMessage.text ?? ''),
168
+ },
169
+ parent_tool_use_id: toolRun.toolUse.id,
170
+ session_id: sessionId,
171
+ });
172
+ }
173
+ emitJson({
174
+ type: 'user',
175
+ message: {
176
+ role: 'user',
177
+ content: [{
178
+ type: 'tool_result',
179
+ tool_use_id: toolRun.toolUse.id,
180
+ content: toolResultContent(toolRun),
181
+ is_error: toolRun.exitCode !== 0,
182
+ }],
183
+ },
184
+ parent_tool_use_id: toolRunParent(toolRun, 'toolResultParentToolUseId', null),
185
+ session_id: sessionId,
186
+ });
187
+ }
188
+ emitJson({
189
+ type: 'assistant',
190
+ message: {
191
+ type: 'message',
192
+ role: 'assistant',
193
+ content: assistantStreamContent(result, parsed),
194
+ stop_reason: 'end_turn',
195
+ usage: estimateUsage(promptText, result),
196
+ },
197
+ parent_tool_use_id: assistantParentToolUseId,
198
+ session_id: sessionId,
199
+ });
200
+ }
201
+
93
202
  export function streamJsonResultMessage({ started, isError, errorSubtype, numTurns, result, sessionId, usage, permissionDenials }) {
94
203
  const common = {
95
204
  type: 'result',