@kkelly-offical/kkcode 0.2.3-preview.2 → 0.2.3

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
@@ -1,6 +1,6 @@
1
1
  # kkcode
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/@kkelly-offical/kkcode?label=v0.2.3-preview.2)](https://www.npmjs.com/package/@kkelly-offical/kkcode)
3
+ [![npm version](https://img.shields.io/npm/v/@kkelly-offical/kkcode?label=v0.2.3)](https://www.npmjs.com/package/@kkelly-offical/kkcode)
4
4
  [![GitHub Release](https://img.shields.io/github/v/release/kkelly-offical/kkcode)](https://github.com/kkelly-offical/kkcode/releases)
5
5
  ![Node](https://img.shields.io/badge/Node.js-%3E%3D22-green)
6
6
  ![License](https://img.shields.io/badge/License-GPL--3.0-blue)
@@ -219,18 +219,25 @@ For a deeper boundary matrix, see [CLI General Assistant Capability Matrix](docs
219
219
 
220
220
  **English**
221
221
  - kkcode supports bounded delegation through the `task` surface.
222
+ - Assistant mode may call subagents directly when the user explicitly asks for one or more agents.
223
+ - Use `task_group` to launch multiple parallel background subagents as one observable group.
224
+ - Use `kkcode agent list --json` to inspect built-in, custom, and configured subagent roles.
222
225
  - Use `fresh_agent` for isolated implementation work.
223
226
  - Use `fork_context` for read-only sidecar work such as research or verification.
224
227
  - Do not outsource core understanding when the main thread must synthesize the result.
225
228
 
226
229
  **中文**
227
230
  - kkcode 通过 `task` 能力支持有边界的委派。
231
+ - 当用户显式要求一个或多个智能体工作时,Assistant 模式可以直接调用子智能体。
232
+ - 使用 `task_group` 可以把多个后台子智能体作为同一个并行组启动和观察。
233
+ - 使用 `kkcode agent list --json` 查看内置、自定义和配置覆盖后的子智能体角色。
228
234
  - `fresh_agent` 适合隔离实现任务。
229
235
  - `fork_context` 适合研究、审计、验证这类只读 sidecar 任务。
230
236
  - 如果主线程必须综合判断,就不要把理解工作本身外包出去。
231
237
 
232
238
  **Background task contract / 后台任务契约**
233
239
  - 通过 `background_output` 查看后台任务输出
240
+ - 通过 `kkcode background parallel` 查看并行子智能体分组和 lane 状态
234
241
  - 通过 `background_cancel` 取消后台任务
235
242
  - 终态固定为 `completed` / `cancelled` / `error` / `interrupted`
236
243
 
@@ -376,18 +383,20 @@ update:
376
383
  <a id="release-status"></a>
377
384
  ## Release Status / 发布状态
378
385
 
379
- **Current preview / 当前预览版本**: `v0.2.3-preview.2`
386
+ **Current stable / 当前稳定版本**: `v0.2.3`
380
387
  **Latest releases / 最新发布**: [GitHub Releases](https://github.com/kkelly-offical/kkcode/releases)
381
388
  **Package / 包地址**: [npm](https://www.npmjs.com/package/@kkelly-offical/kkcode)
382
389
 
383
390
  **English**
384
- - `0.2.3-preview.2` is the Preview V2 context release: compaction now merges prior context state, keeps evidence before pruning, and preserves turn metadata for reliable recent-turn retention.
385
- - `0.2.3-preview.1` is the Preview V1 updater release: kkcode can check npm dist-tags at startup and exposes `kkcode update` for manual upgrades.
391
+ - `0.2.3` is the stable assistant/subagent/context release: Assistant can explicitly delegate to one or many subagents, parallel lanes are observable, updater support is included, and context compaction keeps prior summaries plus recent evidence.
392
+ - `0.2.3-preview.2` validated the context compaction path.
393
+ - `0.2.3-preview.1` validated updater checks and the `kkcode update` command.
386
394
  - `0.2.1` rebuilt kkcode around Assistant as the default general-purpose lane, with dedicated Agent and LongAgent modes for coding work.
387
395
 
388
396
  **中文**
389
- - `0.2.3-preview.2` Preview V2 上下文版本:压缩会合并旧上下文状态,在裁剪前保留关键证据,并保留 turn 元数据以稳定保留最近对话。
390
- - `0.2.3-preview.1` 是 Preview V1 更新器版本:kkcode 可在启动时检查 npm dist-tag,并提供 `kkcode update` 手动升级入口。
397
+ - `0.2.3` 是稳定版 Assistant / 子智能体 / 上下文版本:Assistant 可以显式委派一个或多个子智能体,并行 lane 可观察,包含更新器能力,上下文压缩会保留旧摘要和近期证据。
398
+ - `0.2.3-preview.2` 验证了上下文压缩路径。
399
+ - `0.2.3-preview.1` 验证了更新检查和 `kkcode update` 命令。
391
400
  - `0.2.1` 将 kkcode 重构为以 Assistant 为默认入口的通用个人助手,同时保留专门面向代码工作的 Agent 和 LongAgent 模式。
392
401
 
393
402
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kkelly-offical/kkcode",
3
- "version": "0.2.3-preview.2",
3
+ "version": "0.2.3",
4
4
  "description": "CLI-first personal assistant with dedicated coding and LongAgent modes for governed terminal workflows, MCP integrations, and extensible automation.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.5.2",
@@ -1,17 +1,55 @@
1
1
  import { Command } from "commander"
2
2
  import { buildContext, printContextWarnings } from "../context.mjs"
3
3
  import { LongAgentManager } from "../orchestration/longagent-manager.mjs"
4
+ import { listAgents } from "../agent/agent.mjs"
5
+ import { CustomAgentRegistry } from "../agent/custom-agent-loader.mjs"
4
6
 
5
7
  export function createAgentCommand() {
6
8
  const cmd = new Command("agent").description("inspect subagents and longagent runs")
7
9
 
8
10
  cmd
9
11
  .command("list")
10
- .description("list configured subagents")
11
- .action(async () => {
12
+ .description("list active subagent roles and configured overrides")
13
+ .option("--json", "print structured JSON")
14
+ .option("--configured", "print only config-defined agent.subagents overrides")
15
+ .option("--include-hidden", "include hidden internal roles")
16
+ .action(async (options) => {
12
17
  const ctx = await buildContext()
13
18
  printContextWarnings(ctx)
14
- console.log(JSON.stringify(ctx.configState.config.agent.subagents || {}, null, 2))
19
+ await CustomAgentRegistry.initialize(process.cwd())
20
+ const configured = ctx.configState.config.agent?.subagents || {}
21
+ if (options.configured) {
22
+ console.log(JSON.stringify(configured, null, 2))
23
+ return
24
+ }
25
+ const roles = listAgents({ includeHidden: options.includeHidden === true })
26
+ .filter((agent) => agent.mode === "subagent")
27
+ .map((agent) => {
28
+ const override = configured[agent.name] || null
29
+ return {
30
+ name: agent.name,
31
+ description: agent.description || "",
32
+ permission: agent.permission || "default",
33
+ model: override?.model || agent.model || null,
34
+ providerType: override?.providerType || override?.provider_type || null,
35
+ tools: agent.tools || null,
36
+ custom: agent._customAgent === true,
37
+ scope: agent._scope || null,
38
+ source: agent._source || null,
39
+ configured: Boolean(override)
40
+ }
41
+ })
42
+ if (options.json) {
43
+ console.log(JSON.stringify(roles, null, 2))
44
+ return
45
+ }
46
+ for (const role of roles) {
47
+ const flags = [role.permission, role.custom ? "custom" : "builtin", role.configured ? "configured" : ""].filter(Boolean).join(", ")
48
+ const model = role.model ? " model=" + role.model : ""
49
+ const provider = role.providerType ? " provider=" + role.providerType : ""
50
+ console.log(role.name + " [" + flags + "]" + model + provider)
51
+ if (role.description) console.log(" " + role.description)
52
+ }
15
53
  })
16
54
 
17
55
  cmd
@@ -14,6 +14,9 @@ function printTaskSummary(task) {
14
14
  if (!summary) return
15
15
  console.log(`[${summary.status}] ${summary.id} :: ${summary.description}`)
16
16
  console.log(` attempt=${summary.attempt} subagent=${summary.subagent || "-"} execution_mode=${summary.execution_mode || "-"} session=${summary.session_id || "-"}`)
17
+ if (summary.group_id) {
18
+ console.log(` group=${summary.group_id} label=${summary.group_label || "-"}`)
19
+ }
17
20
  if (summary.interruption_reason) {
18
21
  console.log(` interruption=${summary.interruption_reason}`)
19
22
  }
@@ -56,6 +59,32 @@ export function createBackgroundCommand() {
56
59
  })
57
60
  })
58
61
 
62
+
63
+ cmd
64
+ .command("parallel")
65
+ .description("show delegated background tasks grouped as parallel subagent lanes")
66
+ .option("--json", "print raw JSON")
67
+ .action(async (options) => {
68
+ await withContext(async () => {
69
+ const groups = BackgroundManager.summarizeParallel(await BackgroundManager.list())
70
+ if (options.json) {
71
+ console.log(JSON.stringify(groups, null, 2))
72
+ return
73
+ }
74
+ if (groups.length === 0) {
75
+ console.log("no parallel subagent groups")
76
+ return
77
+ }
78
+ for (const group of groups) {
79
+ console.log("[group] " + group.group_id + " :: " + group.group_label + " total=" + group.total + " active=" + group.active)
80
+ for (const lane of group.lanes) {
81
+ console.log(" [" + lane.status + "] " + lane.id + " subagent=" + (lane.subagent || "-") + " task=" + (lane.logical_task_id || "-") + " session=" + (lane.session_id || "-"))
82
+ if (lane.result_preview) console.log(" preview=" + lane.result_preview)
83
+ }
84
+ }
85
+ })
86
+ })
87
+
59
88
  cmd
60
89
  .command("output")
61
90
  .description("print the terminal result payload for one background task")
package/src/index.mjs CHANGED
File without changes
@@ -70,6 +70,8 @@ function summarizeTask(task) {
70
70
  background_mode: task.backgroundMode || null,
71
71
  subagent: task.payload?.subagent || task.payload?.subagentType || null,
72
72
  execution_mode: task.payload?.executionMode || null,
73
+ group_id: task.payload?.groupId || null,
74
+ group_label: task.payload?.groupLabel || null,
73
75
  session_id: task.payload?.subSessionId || null,
74
76
  parent_session_id: task.payload?.parentSessionId || null,
75
77
  stage_id: task.payload?.stageId || null,
@@ -104,10 +106,47 @@ function summarizeTaskList(tasks = []) {
104
106
  recent_terminal: tasks
105
107
  .filter((task) => TERMINAL_STATES.has(task.status))
106
108
  .slice(0, 3)
107
- .map((task) => summarizeTask(task))
109
+ .map((task) => summarizeTask(task)),
110
+ parallel_groups: summarizeParallelGroups(tasks)
108
111
  }
109
112
  }
110
113
 
114
+ function summarizeParallelGroups(tasks = []) {
115
+ const groups = new Map()
116
+ for (const task of tasks) {
117
+ const payload = task.payload || {}
118
+ const groupId = payload.groupId || payload.parentSessionId || "ungrouped"
119
+ const groupLabel = payload.groupLabel || payload.parentSessionId || "ungrouped"
120
+ if (!groups.has(groupId)) {
121
+ groups.set(groupId, {
122
+ group_id: groupId,
123
+ group_label: groupLabel,
124
+ parent_session_id: payload.parentSessionId || null,
125
+ total: 0,
126
+ active: 0,
127
+ counts: { pending: 0, running: 0, completed: 0, cancelled: 0, error: 0, interrupted: 0 },
128
+ lanes: []
129
+ })
130
+ }
131
+ const group = groups.get(groupId)
132
+ const status = task.status || "unknown"
133
+ group.total += 1
134
+ if (group.counts[status] !== undefined) group.counts[status] += 1
135
+ if (status === "pending" || status === "running") group.active += 1
136
+ group.lanes.push({
137
+ id: task.id,
138
+ status,
139
+ subagent: payload.subagent || payload.subagentType || null,
140
+ execution_mode: payload.executionMode || null,
141
+ logical_task_id: payload.logicalTaskId || null,
142
+ session_id: payload.subSessionId || null,
143
+ description: task.description || "",
144
+ result_preview: extractTaskResultPreview(task)
145
+ })
146
+ }
147
+ return [...groups.values()].sort((a, b) => b.active - a.active || b.total - a.total)
148
+ }
149
+
111
150
  function resolveWorkerTimeoutMs(config = {}, payload = {}) {
112
151
  const raw = Number(payload.workerTimeoutMs || config.background?.worker_timeout_ms || 900000)
113
152
  return Number.isFinite(raw) ? Math.max(1000, raw) : 900000
@@ -430,6 +469,10 @@ export const BackgroundManager = {
430
469
  return summarizeTaskList(tasks)
431
470
  },
432
471
 
472
+ summarizeParallel(tasks) {
473
+ return summarizeParallelGroups(tasks)
474
+ },
475
+
433
476
  async list() {
434
477
  await ensureBackgroundTaskRuntimeDir()
435
478
  return readAllTasks()
@@ -195,7 +195,8 @@ async function ensureDelegatedSession({ executionMode, parentSessionId, subSessi
195
195
 
196
196
  export function createTaskDelegate({ config, parentSessionId, model, providerType, runSubtask }) {
197
197
  return async function delegateTask(args = {}) {
198
- const executionModeResult = normalizeExecutionMode(args.execution_mode)
198
+ const requestedExecutionMode = args.inherit_context === true && !args.execution_mode ? "fork_context" : args.execution_mode
199
+ const executionModeResult = normalizeExecutionMode(requestedExecutionMode)
199
200
  if (executionModeResult.error) return { error: executionModeResult.error }
200
201
  const executionMode = executionModeResult.mode
201
202
  const isolationResult = normalizeIsolation(args.isolation)
@@ -211,7 +212,7 @@ export function createTaskDelegate({ config, parentSessionId, model, providerTyp
211
212
  })
212
213
 
213
214
  const subSessionId = String(args.session_id || `sub_${parentSessionId}_${Date.now()}`)
214
- const prompt = buildDelegationPrompt(args)
215
+ const prompt = buildDelegationPrompt({ ...args, execution_mode: executionMode })
215
216
 
216
217
  const subModel = subagent.model || model
217
218
  const subProvider = subagent.providerType || providerType
@@ -229,7 +230,9 @@ export function createTaskDelegate({ config, parentSessionId, model, providerTyp
229
230
  model: subModel,
230
231
  providerType: subProvider,
231
232
  subagent,
232
- allowQuestion: args.allow_question === true
233
+ allowQuestion: args.allow_question === true,
234
+ groupId: args.group_id || null,
235
+ groupLabel: args.group_label || null
233
236
  })
234
237
  await log(out.reply)
235
238
  if (isCancelled()) return { cancelled: true }
@@ -243,7 +246,9 @@ export function createTaskDelegate({ config, parentSessionId, model, providerTyp
243
246
  reply: out.reply,
244
247
  tool_events: out.toolEvents?.length || 0,
245
248
  file_changes: fileChanges,
246
- edit_feedback: editFeedback
249
+ edit_feedback: editFeedback,
250
+ group_id: args.group_id || null,
251
+ group_label: args.group_label || null
247
252
  }
248
253
  }
249
254
 
@@ -265,7 +270,9 @@ export function createTaskDelegate({ config, parentSessionId, model, providerTyp
265
270
  stageId: args.stage_id || null,
266
271
  logicalTaskId: args.task_id || null,
267
272
  plannedFiles: Array.isArray(args.planned_files) ? args.planned_files : [],
268
- allowQuestion: args.allow_question === true
273
+ allowQuestion: args.allow_question === true,
274
+ groupId: args.group_id || null,
275
+ groupLabel: args.group_label || null
269
276
  },
270
277
  config
271
278
  })
@@ -274,7 +281,9 @@ export function createTaskDelegate({ config, parentSessionId, model, providerTyp
274
281
  status: task.status,
275
282
  session_id: subSessionId,
276
283
  execution_mode: executionMode,
277
- isolation
284
+ isolation,
285
+ group_id: args.group_id || null,
286
+ group_label: args.group_label || null
278
287
  }
279
288
  }
280
289
 
@@ -97,7 +97,7 @@ export async function agentPrompt(agent) {
97
97
  // Layer 4: Mode reminder (stable within mode)
98
98
  export async function modeReminder(mode) {
99
99
  const contractBlock = renderPublicModeContract()
100
- if (mode === "assistant") return `${contractBlock}\n\nAssistant mode active. Treat this as the default CLI personal assistant lane for bounded terminal-native work: local files, logs, system checks, web lookup, Git/GitHub assistance, notes, task organization, and lightweight automation. Escalate to agent/code for explicit coding mutations and to longagent for staged multi-file delivery.`
100
+ if (mode === "assistant") return `${contractBlock}\n\nAssistant mode active. Treat this as the default CLI personal assistant lane for bounded terminal-native work: local files, logs, system checks, web lookup, Git/GitHub assistance, notes, task organization, and lightweight automation. Escalate to agent/code for explicit coding mutations and to longagent for staged multi-file delivery. When the user explicitly asks to summon, call, delegate to, or run one or more subagents, you must use task for one subagent or task_group for multiple parallel subagents. Use inherit_context=true or execution_mode=fork_context only for read-only sidecar work that needs the parent transcript; use fresh_agent for implementation work.`
101
101
  if (mode === "plan") return `${contractBlock}\n\n${await loadSessionPrompt("plan.txt")}`
102
102
  if (mode === "agent") return `${contractBlock}\n\n${await loadSessionPrompt("agent.txt")}\n\nCoding lane active. Focus on inspect/patch/verify coding work, keep diffs small, and validate with the narrowest useful tests.`
103
103
  if (mode === "longagent") {
@@ -244,7 +244,7 @@ export async function buildSystemPromptBlocks({ mode, model, cwd, agent = null,
244
244
 
245
245
  // Block 5.5: Available sub-agents (stable — changes only when custom agents change)
246
246
  const allAgents = listAgents({ includeHidden: false })
247
- const customSubagents = allAgents.filter((a) => a.mode === "subagent" && a._customAgent)
247
+ const customSubagents = allAgents.filter((a) => a.mode === "subagent" && a.hidden !== true)
248
248
  if (customSubagents.length) {
249
249
  const agentLines = customSubagents.map((a) => {
250
250
  const perms = a.permission === "readonly" ? " (read-only)" : a.permission === "full" ? " (full access)" : ""
@@ -254,7 +254,7 @@ export async function buildSystemPromptBlocks({ mode, model, cwd, agent = null,
254
254
  "# Available Sub-agents",
255
255
  "",
256
256
  "Delegate specialized work to these sub-agents using the `task` tool with `subagent_type` parameter.",
257
- "Use sub-agents when a task is self-contained and would benefit from a specialist, or to save context window space.",
257
+ "Use sub-agents when a task is self-contained and would benefit from a specialist, to save context window space, or whenever the user explicitly asks for subagents. Use task_group for multiple independent lanes.",
258
258
  "",
259
259
  ...agentLines
260
260
  ].join("\n")
@@ -0,0 +1,10 @@
1
+ Use `task_group` when the user explicitly asks for multiple subagents, parallel agents, separate specialist lanes, or a comparison between independent agent findings.
2
+
3
+ Behavior:
4
+ - Launches every lane as a background delegated task.
5
+ - Returns a shared `group_id`, `group_label`, per-lane task ids, and a compact visualization payload.
6
+ - Use `inherit_context=true` or `execution_mode="fork_context"` only for read-only sidecar work that needs the parent transcript.
7
+ - Use `fresh_agent` for implementation or mutation work.
8
+ - Provide `subagent_type`, `objective` or `prompt`, `write_scope`, and `deliverable` for each lane when possible.
9
+
10
+ After launching a group, use `task_parallel`, `task_list`, or `task_output` to observe progress and collect results.
@@ -0,0 +1,3 @@
1
+ Use task_parallel to inspect delegated background tasks grouped as parallel subagent lanes.
2
+
3
+ It returns groups with group_id, group_label, aggregate status counts, active lane counts, and per-lane status/result previews. Use it after task_group or when summarizing concurrent subagent progress for the user.
@@ -8,7 +8,7 @@ import { pathToFileURL } from "node:url"
8
8
  import { atomicWriteFile, replaceInFileTransactional, replaceAllInFileTransactional, diffLineCount, buildStructuredPatch } from "./edit-transaction.mjs"
9
9
  import { withFileLock } from "./file-lock-manager.mjs"
10
10
  import { BackgroundManager } from "../orchestration/background-manager.mjs"
11
- import { createTaskTool } from "./task-tool.mjs"
11
+ import { createTaskTool, createTaskGroupTool } from "./task-tool.mjs"
12
12
  import { McpRegistry } from "../mcp/registry.mjs"
13
13
  import { SkillRegistry } from "../skill/registry.mjs"
14
14
  import { askQuestionInteractive } from "./question-prompt.mjs"
@@ -881,6 +881,17 @@ function builtinTools(config) {
881
881
  }
882
882
  }
883
883
 
884
+
885
+ const taskParallelTool = {
886
+ name: "task_parallel",
887
+ description: "Show delegated background tasks grouped as parallel subagent lanes.",
888
+ inputSchema: { type: "object", properties: {}, required: [] },
889
+ async execute() {
890
+ const tasks = await BackgroundManager.list()
891
+ return BackgroundManager.summarizeParallel(tasks)
892
+ }
893
+ }
894
+
884
895
  const taskGetTool = {
885
896
  name: "task_get",
886
897
  description: "Retrieve one delegated background task summary and result payload by task_id.",
@@ -1570,7 +1581,7 @@ function builtinTools(config) {
1570
1581
  const gitTools = config?.git_auto?.enabled !== false ? gitAutoTools : []
1571
1582
  const gitFullAutoToolsList = config?.git_auto?.full_auto === true ? gitFullAutoTools : []
1572
1583
 
1573
- return [listTool, sysinfoTool, readTool, writeTool, editTool, patchTool, multieditTool, globTool, grepTool, bashTool, createTaskTool(), outputTool, cancelTool, taskListTool, taskGetTool, taskStopTool, taskOutputTool, todowriteTool, questionTool, skillTool, webfetchTool, websearchTool, codesearchTool, notebookeditTool, enterPlanTool, exitPlanTool, ...gitTools, ...gitFullAutoToolsList]
1584
+ return [listTool, sysinfoTool, readTool, writeTool, editTool, patchTool, multieditTool, globTool, grepTool, bashTool, createTaskTool(), createTaskGroupTool(), outputTool, cancelTool, taskListTool, taskParallelTool, taskGetTool, taskStopTool, taskOutputTool, todowriteTool, questionTool, skillTool, webfetchTool, websearchTool, codesearchTool, notebookeditTool, enterPlanTool, exitPlanTool, ...gitTools, ...gitFullAutoToolsList]
1574
1585
  }
1575
1586
 
1576
1587
  function mcpTools() {
@@ -1593,7 +1604,7 @@ function mcpTools() {
1593
1604
 
1594
1605
  function toolAllowedByMode(toolName, mode) {
1595
1606
  if (mode === "plan") {
1596
- return !["write", "edit", "patch", "multiedit", "notebookedit", "bash", "task", "git_snapshot", "git_restore", "git_apply_patch", "git_delete_snapshot"].includes(toolName)
1607
+ return !["write", "edit", "patch", "multiedit", "notebookedit", "bash", "task", "task_group", "git_snapshot", "git_restore", "git_apply_patch", "git_delete_snapshot"].includes(toolName)
1597
1608
  }
1598
1609
  return true
1599
1610
  }
@@ -1,36 +1,90 @@
1
+ function taskProperties() {
2
+ return {
3
+ prompt: { type: "string", description: "self-contained task brief for the delegated subagent" },
4
+ objective: { type: "string", description: "primary outcome to achieve when synthesizing a delegation brief" },
5
+ why: { type: "string", description: "context or decision pressure behind the delegated work" },
6
+ write_scope: { type: "string", description: "explicit write scope such as read-only, specific files, or no mutations" },
7
+ description: { type: "string", description: "short task description for background task tracking" },
8
+ subagent_type: { type: "string", description: "explicit subagent type" },
9
+ category: { type: "string", description: "routing category" },
10
+ session_id: { type: "string", description: "continue from an existing delegated sub-session instead of starting fresh" },
11
+ stage_id: { type: "string", description: "optional stage id for orchestration" },
12
+ task_id: { type: "string", description: "optional logical task id" },
13
+ group_id: { type: "string", description: "optional parallel group id for related delegated tasks" },
14
+ group_label: { type: "string", description: "optional human-readable parallel group label" },
15
+ starting_points: { type: "array", items: { type: "string" }, description: "relevant files, symbols, tests, or commands the subagent should start from" },
16
+ constraints: { type: "array", items: { type: "string" }, description: "architectural boundaries, forbidden edits, or safety constraints for the delegated run" },
17
+ deliverable: { type: "string", description: "expected output from the subagent, such as a patch, findings, or a concise summary" },
18
+ execution_mode: { type: "string", enum: ["fresh_agent", "fork_context"], description: "delegation mode: fresh_agent for isolated work, fork_context for read-only sidecars that inherit the parent transcript" },
19
+ inherit_context: { type: "boolean", description: "shortcut for execution_mode=fork_context; only valid for read-only sidecar work" },
20
+ isolation: { type: "string", enum: ["default", "worktree"], description: "execution isolation for delegated work" },
21
+ run_in_background: { type: "boolean", description: "run async in background for non-blocking sidecar work" },
22
+ planned_files: { type: "array", items: { type: "string" }, description: "planned files for this task" },
23
+ allow_question: { type: "boolean", description: "allow question tool during delegated run; foreground only" }
24
+ }
25
+ }
26
+
27
+ function newGroupId() {
28
+ return "grp_" + Date.now().toString(36) + "_" + Math.random().toString(36).slice(2, 8)
29
+ }
30
+
1
31
  export function createTaskTool() {
2
32
  return {
3
33
  name: "task",
4
- description: "Delegate complex multi-step work to a subagent that makes its own LLM calls. Prefer staying local for simple edits or critical-path work that immediately depends on your next action. New delegated runs start with fresh context unless you explicitly set execution_mode='fork_context' or continue an existing sub-session via session_id. `fork_context` is reserved for read-only sidecar work such as audits, reviews, and follow-up verification. You may provide either a fully written prompt or structured brief fields (objective/why/write_scope/starting_points/constraints/deliverable) for kkcode to synthesize a directive delegation brief. IMPORTANT: Do NOT use this for simple file operations — use 'write' to create files and 'edit' to modify files directly. Background tasks (run_in_background) spawn a separate worker process for sidecar work, cannot ask interactive questions, and must be observed via deterministic status/result retrieval.",
34
+ description: "Delegate complex multi-step work to a subagent that makes its own LLM calls. Use inherit_context=true or execution_mode=fork_context for read-only sidecars that need the parent transcript. Background tasks spawn a separate worker process and must be observed via task_list/task_output.",
35
+ inputSchema: { type: "object", properties: taskProperties(), required: [] },
36
+ async execute(args, ctx) {
37
+ if (typeof ctx.delegateTask === "function") return ctx.delegateTask(args || {})
38
+ return { error: "task delegate unavailable" }
39
+ }
40
+ }
41
+ }
42
+
43
+ export function createTaskGroupTool() {
44
+ return {
45
+ name: "task_group",
46
+ description: "Launch multiple delegated subagents as one parallel background group. Use this when the user explicitly asks to summon multiple agents, run agents in parallel, split research/review/verification lanes, or compare independent specialist findings.",
5
47
  inputSchema: {
6
48
  type: "object",
7
49
  properties: {
8
- prompt: { type: "string", description: "self-contained task brief for the delegated subagent" },
9
- objective: { type: "string", description: "primary outcome to achieve when synthesizing a delegation brief" },
10
- why: { type: "string", description: "context or decision pressure behind the delegated work" },
11
- write_scope: { type: "string", description: "explicit write scope such as read-only, specific files, or no mutations" },
12
- starting_points: { type: "array", items: { type: "string" }, description: "relevant files, symbols, tests, or commands the subagent should start from" },
13
- constraints: { type: "array", items: { type: "string" }, description: "architectural boundaries, forbidden edits, or safety constraints for the delegated run" },
14
- deliverable: { type: "string", description: "expected output from the subagent, such as a patch, findings, or a concise summary" },
15
- description: { type: "string", description: "short task description for background task tracking" },
16
- subagent_type: { type: "string", description: "explicit subagent type" },
17
- category: { type: "string", description: "routing category" },
18
- execution_mode: { type: "string", enum: ["fresh_agent", "fork_context"], description: "delegation mode: 'fresh_agent' (default) for implementation or isolated new work, or 'fork_context' for read-only sidecar work that must inherit the parent transcript" },
19
- isolation: { type: "string", enum: ["default", "worktree"], description: "execution isolation: 'default' runs in the main workspace, 'worktree' creates a local detached git worktree for isolated background sidecar execution" },
20
- run_in_background: { type: "boolean", description: "run async in background for non-blocking sidecar work; background delegates cannot ask interactive questions" },
21
- session_id: { type: "string", description: "continue from an existing delegated sub-session instead of starting fresh" },
22
- stage_id: { type: "string", description: "optional stage id for orchestration" },
23
- task_id: { type: "string", description: "optional logical task id" },
24
- planned_files: { type: "array", items: { type: "string" }, description: "planned files for this task" },
25
- allow_question: { type: "boolean", description: "allow question tool during delegated run; foreground only, not supported for run_in_background=true" }
50
+ group_id: { type: "string", description: "optional stable parallel group id" },
51
+ group_label: { type: "string", description: "human-readable label for the parallel group" },
52
+ inherit_context: { type: "boolean", description: "default context inheritance for tasks that do not specify execution_mode" },
53
+ execution_mode: { type: "string", enum: ["fresh_agent", "fork_context"], description: "default execution mode for tasks" },
54
+ isolation: { type: "string", enum: ["default", "worktree"], description: "default isolation mode for tasks" },
55
+ tasks: { type: "array", description: "subagent tasks to launch in parallel", items: { type: "object", properties: taskProperties(), required: [] } }
26
56
  },
27
- required: []
57
+ required: ["tasks"]
28
58
  },
29
59
  async execute(args, ctx) {
30
- if (typeof ctx.delegateTask !== "function") {
31
- return { error: "task delegate unavailable" }
60
+ if (typeof ctx.delegateTask !== "function") return { error: "task delegate unavailable" }
61
+ const tasks = Array.isArray(args?.tasks) ? args.tasks : []
62
+ if (tasks.length === 0) return { error: "task_group.tasks must contain at least one task" }
63
+ const groupId = String(args.group_id || newGroupId())
64
+ const groupLabel = String(args.group_label || "parallel subagents")
65
+ const results = []
66
+ for (let i = 0; i < tasks.length; i++) {
67
+ const task = tasks[i] || {}
68
+ const merged = {
69
+ ...task,
70
+ group_id: task.group_id || groupId,
71
+ group_label: task.group_label || groupLabel,
72
+ run_in_background: true,
73
+ inherit_context: task.inherit_context ?? args.inherit_context,
74
+ execution_mode: task.execution_mode || args.execution_mode,
75
+ isolation: task.isolation || args.isolation || "default",
76
+ description: task.description || task.objective || task.prompt || groupLabel,
77
+ task_id: task.task_id || "lane_" + (i + 1)
78
+ }
79
+ results.push(await ctx.delegateTask(merged))
80
+ }
81
+ return {
82
+ group_id: groupId,
83
+ group_label: groupLabel,
84
+ status: results.some((r) => r?.error) ? "partial_error" : "launched",
85
+ tasks: results,
86
+ visualization: results.map((result, index) => ({ lane: index + 1, task_id: result.background_task_id || null, subagent: tasks[index]?.subagent_type || tasks[index]?.category || "default-subagent", status: result.status || (result.error ? "error" : "unknown"), session_id: result.session_id || null, error: result.error || null }))
32
87
  }
33
- return ctx.delegateTask(args || {})
34
88
  }
35
89
  }
36
90
  }
@@ -1,7 +1,12 @@
1
1
  export function renderBackgroundSummaryPanel(backgroundSummary) {
2
2
  if (!backgroundSummary) return []
3
- return [
3
+ const groups = Array.isArray(backgroundSummary.parallel_groups) ? backgroundSummary.parallel_groups : []
4
+ const activeGroups = groups.filter((group) => Number(group.active || 0) > 0).length
5
+ const activeLanes = groups.reduce((sum, group) => sum + Number(group.active || 0), 0)
6
+ const lines = [
4
7
  `background=${backgroundSummary.active} active (pending:${backgroundSummary.counts.pending}, running:${backgroundSummary.counts.running})`,
5
8
  `background.terminal=completed:${backgroundSummary.counts.completed} interrupted:${backgroundSummary.counts.interrupted} error:${backgroundSummary.counts.error}`
6
9
  ]
10
+ if (groups.length) lines.push(`parallel=${activeGroups} active group(s), ${activeLanes} active lane(s)`)
11
+ return lines
7
12
  }
package/src/version.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  export const PACKAGE_NAME = "@kkelly-offical/kkcode"
2
- export const PACKAGE_VERSION = "0.2.3-preview.2"
3
- export const RELEASE_LABEL = "0.2.3 Preview V2"
2
+ export const PACKAGE_VERSION = "0.2.3"
3
+ export const RELEASE_LABEL = "0.2.3"