@shipers-dev/multi 0.39.2 → 0.43.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 CHANGED
@@ -17109,7 +17109,7 @@ function setWorktreeIndexEntry(workingDir, key, issueId) {
17109
17109
  idx[key] = issueId;
17110
17110
  writeIndex(workingDir, idx);
17111
17111
  }
17112
- async function ensureWorktree(workingDir, issueKey, issueId) {
17112
+ async function ensureWorktree(workingDir, issueKey, issueId, opts) {
17113
17113
  if (!await isGitRepo(workingDir)) {
17114
17114
  return { path: workingDir, branch: "", created: false };
17115
17115
  }
@@ -17126,8 +17126,19 @@ async function ensureWorktree(workingDir, issueKey, issueId) {
17126
17126
  try {
17127
17127
  mkdirSync3(wtDir, { recursive: true });
17128
17128
  } catch {}
17129
+ let baseRef = "HEAD";
17130
+ if (opts?.parentBranch) {
17131
+ const localOk = await branchExists(workingDir, opts.parentBranch);
17132
+ if (localOk) {
17133
+ baseRef = opts.parentBranch;
17134
+ } else {
17135
+ const fetched = await run3(workingDir, "git", ["fetch", "origin", `${opts.parentBranch}:${opts.parentBranch}`]);
17136
+ if (fetched.code === 0)
17137
+ baseRef = opts.parentBranch;
17138
+ }
17139
+ }
17129
17140
  const exists4 = await branchExists(workingDir, branch);
17130
- const args2 = exists4 ? ["worktree", "add", wtPath, branch] : ["worktree", "add", "-b", branch, wtPath, "HEAD"];
17141
+ const args2 = exists4 ? ["worktree", "add", wtPath, branch] : ["worktree", "add", "-b", branch, wtPath, baseRef];
17131
17142
  const r = await run3(workingDir, "git", args2);
17132
17143
  if (r.code !== 0) {
17133
17144
  throw new Error(`git worktree add failed: ${r.stderr || r.stdout}`);
@@ -17219,6 +17230,48 @@ async function removeWorktree(workingDir, issueKey, opts = {}) {
17219
17230
  removeWorktreeIndexEntry(workingDir, key);
17220
17231
  return result;
17221
17232
  }
17233
+ async function squashMergeChild(wtPath, childBranch, childKey) {
17234
+ const result = { childKey, branch: childBranch, ok: false, empty: false, conflicts: [], message: "" };
17235
+ await run3(wtPath, "git", ["fetch", "origin", `${childBranch}:${childBranch}`]).catch(() => null);
17236
+ const haveLocal = await branchExists(wtPath, childBranch);
17237
+ if (!haveLocal) {
17238
+ result.message = `branch ${childBranch} not resolvable locally or via origin`;
17239
+ return result;
17240
+ }
17241
+ const ahead = await run3(wtPath, "git", ["rev-list", "--count", `HEAD..${childBranch}`]);
17242
+ if (ahead.code === 0 && Number((ahead.stdout || "0").trim()) === 0) {
17243
+ result.ok = true;
17244
+ result.empty = true;
17245
+ result.message = `${childKey} had no commits ahead of parent — nothing to merge`;
17246
+ return result;
17247
+ }
17248
+ const merge9 = await run3(wtPath, "git", ["merge", "--squash", "--no-commit", childBranch]);
17249
+ const conflicted = await run3(wtPath, "git", ["diff", "--name-only", "--diff-filter=U"]);
17250
+ const conflictPaths = (conflicted.stdout || "").split(`
17251
+ `).map((l) => l.trim()).filter(Boolean);
17252
+ if (merge9.code !== 0 || conflictPaths.length > 0) {
17253
+ result.conflicts = conflictPaths;
17254
+ result.message = `${childKey} had ${conflictPaths.length} conflicting path(s); worktree left dirty for parent to resolve`;
17255
+ return result;
17256
+ }
17257
+ const staged = await run3(wtPath, "git", ["diff", "--cached", "--name-only"]);
17258
+ const stagedFiles = (staged.stdout || "").split(`
17259
+ `).map((l) => l.trim()).filter(Boolean);
17260
+ if (stagedFiles.length === 0) {
17261
+ result.ok = true;
17262
+ result.empty = true;
17263
+ result.message = `${childKey} produced no staged changes — nothing to commit`;
17264
+ return result;
17265
+ }
17266
+ const commit = await run3(wtPath, "git", ["commit", "-m", `squash: ${childKey} (${childBranch})`]);
17267
+ if (commit.code !== 0) {
17268
+ result.message = `${childKey} commit failed: ${commit.stderr || commit.stdout}`;
17269
+ return result;
17270
+ }
17271
+ result.ok = true;
17272
+ result.message = `${childKey} squash-merged (${stagedFiles.length} file${stagedFiles.length === 1 ? "" : "s"})`;
17273
+ return result;
17274
+ }
17222
17275
  var init_worktree = () => {};
17223
17276
 
17224
17277
  // ../../node_modules/zod/v4/core/core.js
@@ -33802,7 +33855,7 @@ function parsePlanBlocks(text) {
33802
33855
  }
33803
33856
  return { actions, errors: errors3 };
33804
33857
  }
33805
- var PLAN_SCHEMA_VERSION = 2, Priority, AssigneeType, IssueStatus, SessionRole, SkillFile, PlanActionSchema, PlanEnvelopeSchema, UiBlockSchema, UI_FENCE_RE, FENCE_RE;
33858
+ var PLAN_SCHEMA_VERSION = 5, Priority, AssigneeType, IssueStatus, SessionRole, SkillFile, EvalPolicy, PlanActionSchema, PlanEnvelopeSchema, UiBlockSchema, UI_FENCE_RE, FENCE_RE;
33806
33859
  var init_plans = __esm(() => {
33807
33860
  init_zod();
33808
33861
  Priority = exports_external.enum(["low", "medium", "high"]);
@@ -33810,6 +33863,12 @@ var init_plans = __esm(() => {
33810
33863
  IssueStatus = exports_external.enum(["todo", "in_progress", "done", "failed", "stopped", "cancelled"]);
33811
33864
  SessionRole = exports_external.enum(["implementer", "reviewer", "test-fixer"]);
33812
33865
  SkillFile = exports_external.object({ path: exports_external.string().min(1), content: exports_external.string() });
33866
+ EvalPolicy = exports_external.object({
33867
+ evaluator_agent_id: exports_external.string().min(1),
33868
+ threshold: exports_external.number().min(0).max(1),
33869
+ on_fail: exports_external.enum(["retry", "fail", "comment"]).optional(),
33870
+ max_retries: exports_external.number().int().min(0).max(10).optional()
33871
+ });
33813
33872
  PlanActionSchema = exports_external.discriminatedUnion("type", [
33814
33873
  exports_external.object({
33815
33874
  type: exports_external.literal("create"),
@@ -33820,7 +33879,9 @@ var init_plans = __esm(() => {
33820
33879
  assignee_type: AssigneeType.optional(),
33821
33880
  assignee_id: exports_external.string().optional(),
33822
33881
  parent_id: exports_external.string().optional(),
33823
- blocked_by: exports_external.array(exports_external.string().min(1)).optional()
33882
+ blocked_by: exports_external.array(exports_external.string().min(1)).optional(),
33883
+ await_children: exports_external.boolean().optional(),
33884
+ eval_policy: EvalPolicy.optional()
33824
33885
  }),
33825
33886
  exports_external.object({
33826
33887
  type: exports_external.literal("update"),
@@ -33831,7 +33892,8 @@ var init_plans = __esm(() => {
33831
33892
  priority: Priority.optional(),
33832
33893
  assignee_type: AssigneeType.optional(),
33833
33894
  assignee_id: exports_external.string().optional(),
33834
- blocked_by: exports_external.array(exports_external.string().min(1)).optional()
33895
+ blocked_by: exports_external.array(exports_external.string().min(1)).optional(),
33896
+ eval_policy: EvalPolicy.nullable().optional()
33835
33897
  }),
33836
33898
  exports_external.object({
33837
33899
  type: exports_external.literal("delegate"),
@@ -33870,7 +33932,7 @@ var init_plans = __esm(() => {
33870
33932
  exports_external.object({
33871
33933
  type: exports_external.literal("agent.create"),
33872
33934
  name: exports_external.string().min(1).max(120),
33873
- agent_type: exports_external.string().min(1),
33935
+ agent_type: exports_external.string().min(1).optional(),
33874
33936
  prompt: exports_external.string().optional(),
33875
33937
  skill_ids: exports_external.array(exports_external.string()).optional(),
33876
33938
  allowed_tools: exports_external.array(exports_external.string()).optional()
@@ -33906,6 +33968,13 @@ var init_plans = __esm(() => {
33906
33968
  agent_id: exports_external.string().min(1),
33907
33969
  role: SessionRole,
33908
33970
  device_id: exports_external.string().optional()
33971
+ }),
33972
+ exports_external.object({
33973
+ type: exports_external.literal("eval.submit"),
33974
+ id: exports_external.string().min(1).optional(),
33975
+ score: exports_external.number().min(0).max(1),
33976
+ feedback: exports_external.string().min(1).max(8000),
33977
+ scores: exports_external.record(exports_external.string(), exports_external.number().min(0).max(1)).optional()
33909
33978
  })
33910
33979
  ]);
33911
33980
  PlanEnvelopeSchema = exports_external.object({
@@ -34341,7 +34410,7 @@ async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
34341
34410
  let worktreeBranch = "";
34342
34411
  if (baseWorkingDir) {
34343
34412
  try {
34344
- const wt = await ensureWorktree(baseWorkingDir, task.key || issueId, issueId);
34413
+ const wt = await ensureWorktree(baseWorkingDir, task.key || issueId, issueId, { parentBranch: task.parent_branch ?? undefined });
34345
34414
  workingDir = wt.path;
34346
34415
  worktreeBranch = wt.branch;
34347
34416
  await postStream(apiUrl, issueId, "worktree_created", { path: wt.path, branch: wt.branch, reused: !wt.created });
@@ -34356,6 +34425,44 @@ async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
34356
34425
  await postStream(apiUrl, issueId, "worktree_error", { message: fmtError(e) });
34357
34426
  }
34358
34427
  }
34428
+ let mergeReportLines = [];
34429
+ let mergeHadConflicts = false;
34430
+ const mergeTargets = Array.isArray(task.merge_targets) ? task.merge_targets : [];
34431
+ if (workingDir && mergeTargets.length) {
34432
+ for (const t of mergeTargets) {
34433
+ if (!t?.branch || !t?.child_key)
34434
+ continue;
34435
+ try {
34436
+ const r = await squashMergeChild(workingDir, String(t.branch), String(t.child_key));
34437
+ if (r.ok && r.empty) {
34438
+ mergeReportLines.push(`- _empty_ ${t.child_key} → no changes`);
34439
+ } else if (r.ok) {
34440
+ mergeReportLines.push(`- ✅ ${t.child_key} squashed into ${worktreeBranch || "(parent)"}`);
34441
+ } else if (r.conflicts.length) {
34442
+ mergeHadConflicts = true;
34443
+ mergeReportLines.push(`- ⚠ ${t.child_key} CONFLICT in: ${r.conflicts.join(", ")}`);
34444
+ } else {
34445
+ mergeReportLines.push(`- ✗ ${t.child_key} ${r.message}`);
34446
+ }
34447
+ await postStream(apiUrl, issueId, "child_merge", {
34448
+ child_key: t.child_key,
34449
+ branch: t.branch,
34450
+ ok: r.ok,
34451
+ empty: r.empty,
34452
+ conflicts: r.conflicts,
34453
+ message: r.message
34454
+ });
34455
+ } catch (e) {
34456
+ mergeReportLines.push(`- ✗ ${t.child_key} merge threw: ${fmtError(e)}`);
34457
+ }
34458
+ }
34459
+ if (mergeReportLines.length) {
34460
+ const header = mergeHadConflicts ? "## ⚠ Squash-merge results — conflicts to resolve in your worktree" : "## Squash-merge results";
34461
+ const block = [header, ...mergeReportLines, "", "---", ""].join(`
34462
+ `);
34463
+ task.followup = block + (task.followup || "");
34464
+ }
34465
+ }
34359
34466
  log3(`▶ run_task ${task.key}: ${isFollowup ? "(follow-up) " : ""}${task.title}${workingDir ? ` [cwd: ${workingDir}${worktreeBranch ? ` @${worktreeBranch}` : ""}]` : ""}`);
34360
34467
  if (tenantWsId)
34361
34468
  await patchIssueStatus(apiUrl, tenantWsId, issueId, "in_progress");
@@ -34925,7 +35032,7 @@ Agent + skill self-service (use sparingly — only when you genuinely need a new
34925
35032
 
34926
35033
  \`\`\`multi-plan
34927
35034
  {"actions":[
34928
- {"type":"agent.create","name":"refactor-bot","agent_type":"claude-code","prompt":"You refactor TS code...","skill_ids":["sk_xxx"],"allowed_tools":["Read","Edit","Bash"]},
35035
+ {"type":"agent.create","name":"refactor-bot","prompt":"You refactor TS code...","skill_ids":["sk_xxx"],"allowed_tools":["Read","Edit","Bash"]},
34929
35036
  {"type":"agent.update","id":"ag_xxx","prompt":"new prompt..."},
34930
35037
  {"type":"skill.create","name":"run-tests","description":"Run the test suite","body":"---\\nname: run-tests\\n---\\n\\n# Run tests\\n..."},
34931
35038
  {"type":"skill.attach","agent_id":"ag_xxx","skill_id":"sk_yyy"},
@@ -34950,8 +35057,11 @@ ${agentsBlock}
34950
35057
  }
34951
35058
  async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors = []) {
34952
35059
  const lines = [];
34953
- for (const e of parseErrors)
35060
+ const results = [];
35061
+ for (const e of parseErrors) {
34954
35062
  lines.push(`- [err] plan parse: ${e.message}`);
35063
+ results.push({ type: "parse", status: "error", error: e.message });
35064
+ }
34955
35065
  let truncated = false;
34956
35066
  if (actions.length > PLAN_ACTION_LIMIT) {
34957
35067
  truncated = true;
@@ -34961,8 +35071,10 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
34961
35071
  if (depth >= PLANNING_DEPTH_LIMIT) {
34962
35072
  const blocked3 = actions.filter((a) => a.type !== "update").length;
34963
35073
  actions = actions.filter((a) => a.type === "update");
34964
- if (blocked3)
35074
+ if (blocked3) {
34965
35075
  lines.push(`- [warn] ${blocked3} non-update action(s) blocked (planning depth limit ${PLANNING_DEPTH_LIMIT})`);
35076
+ results.push({ type: "note", status: "note", message: `${blocked3} non-update action(s) blocked (planning depth limit ${PLANNING_DEPTH_LIMIT})` });
35077
+ }
34966
35078
  }
34967
35079
  const SUBCAPS = { "agent.create": 2, "skill.create": 3, "skill.attach": 5, "skill.detach": 5, "agent.update": 5, "session.create": 3 };
34968
35080
  const counts = {};
@@ -34973,6 +35085,7 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
34973
35085
  counts[a.type] = (counts[a.type] || 0) + 1;
34974
35086
  if (counts[a.type] > cap) {
34975
35087
  lines.push(`- [warn] ${a.type} sub-cap ${cap} hit, dropping extra`);
35088
+ results.push({ type: a.type, status: "note", message: `${a.type} sub-cap ${cap} hit, dropping extra` });
34976
35089
  return false;
34977
35090
  }
34978
35091
  return true;
@@ -35008,22 +35121,27 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
35008
35121
  priority: a.priority,
35009
35122
  assignee_type: a.assignee_type,
35010
35123
  assignee_id: a.assignee_id,
35011
- parent_id: a.parent_id || parentId
35124
+ parent_id: a.parent_id || parentId,
35125
+ await_children: a.await_children
35012
35126
  };
35013
35127
  const res = await apiClient.post(mutateUrl, body, { headers });
35014
35128
  if (!res.success) {
35015
35129
  lines.push(`- [err] create "${a.title}": ${res.error || res.status}`);
35130
+ results.push({ type: "create", status: "error", error: String(res.error || res.status), label: a.title });
35016
35131
  continue;
35017
35132
  }
35018
35133
  const created = res.data;
35019
35134
  lines.push(`- [ok] created **${created.key}** - ${created.title}${created.assignee_id ? ` -> @${created.assignee_id}` : ""} (autonomy=${created.autonomy_level || "auto"})`);
35135
+ results.push({ type: "create", status: "ok", issue_id: created.id, key: created.key, title: created.title, assignee_id: created.assignee_id ?? null, assignee_type: created.assignee_type ?? null, priority: created.priority ?? null, autonomy_level: created.autonomy_level || "auto" });
35020
35136
  } else if (a.type === "update") {
35021
35137
  const res = await apiClient.post(mutateUrl, { action: "update", ...a }, { headers });
35022
35138
  if (!res.success) {
35023
35139
  lines.push(`- [err] update ${a.id}: ${res.error || res.status}`);
35140
+ results.push({ type: "update", status: "error", error: String(res.error || res.status), label: a.id });
35024
35141
  continue;
35025
35142
  }
35026
35143
  lines.push(`- [ok] updated ${res.data.key}`);
35144
+ results.push({ type: "update", status: "ok", issue_id: res.data.id, key: res.data.key, status_to: a.status ?? null, title_to: a.title ?? null });
35027
35145
  if ((a.status === "done" || a.status === "cancelled") && parentTask.working_dir && existsSync11(parentTask.working_dir)) {
35028
35146
  const targetKey = res.data?.key;
35029
35147
  if (targetKey) {
@@ -35038,9 +35156,11 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
35038
35156
  const res = await apiClient.post(mutateUrl, { action: "update", id: a.id, assignee_type: "agent", assignee_id: a.assignee_id, status: "todo" }, { headers });
35039
35157
  if (!res.success) {
35040
35158
  lines.push(`- [err] delegate ${a.id}: ${res.error || res.status}`);
35159
+ results.push({ type: "delegate", status: "error", error: String(res.error || res.status), label: a.id });
35041
35160
  continue;
35042
35161
  }
35043
35162
  lines.push(`- [ok] delegated ${res.data.key} -> ${a.assignee_id}`);
35163
+ results.push({ type: "delegate", status: "ok", issue_id: res.data.id, key: res.data.key, assignee_id: a.assignee_id });
35044
35164
  } else if (a.type === "handoff") {
35045
35165
  const res = await apiClient.post(mutateUrl, {
35046
35166
  action: "handoff",
@@ -35052,17 +35172,21 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
35052
35172
  }, { headers });
35053
35173
  if (!res.success) {
35054
35174
  lines.push(`- [err] handoff -> ${a.target_agent_id}: ${res.error || res.status}`);
35175
+ results.push({ type: "handoff", status: "error", error: String(res.error || res.status), label: a.target_agent_id });
35055
35176
  continue;
35056
35177
  }
35057
35178
  const blocked3 = res.data?.blocked ? " (blocked parent)" : "";
35058
35179
  lines.push(`- [ok] handoff -> ${a.target_agent_id} via **${res.data?.child_key}**${blocked3} expect=${a.expect ?? "summary"}`);
35180
+ results.push({ type: "handoff", status: "ok", issue_id: res.data?.child_id, key: res.data?.child_key, assignee_id: a.target_agent_id, blocked: !!res.data?.blocked, expect: a.expect ?? "summary" });
35059
35181
  } else if (a.type === "issue.delete") {
35060
35182
  const res = await apiClient.post(mutateUrl, { action: "delete", id: a.id }, { headers });
35061
35183
  if (!res.success) {
35062
35184
  lines.push(`- [err] issue.delete ${a.id}: ${res.error || res.status}`);
35185
+ results.push({ type: "issue.delete", status: "error", error: String(res.error || res.status), label: a.id });
35063
35186
  continue;
35064
35187
  }
35065
35188
  lines.push(`- [ok] deleted ${res.data?.key || a.id}`);
35189
+ results.push({ type: "issue.delete", status: "ok", key: res.data?.key || a.id, issue_id: res.data?.id || a.id });
35066
35190
  } else if (a.type === "issue.delete_where") {
35067
35191
  const res = await apiClient.post(mutateUrl, {
35068
35192
  action: "delete_where",
@@ -35073,17 +35197,20 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
35073
35197
  }, { headers });
35074
35198
  if (!res.success) {
35075
35199
  lines.push(`- [err] issue.delete_where: ${res.error || res.status}`);
35200
+ results.push({ type: "issue.delete_where", status: "error", error: String(res.error || res.status) });
35076
35201
  continue;
35077
35202
  }
35078
35203
  const rows = Array.isArray(res.data?.deleted) ? res.data.deleted : [];
35079
35204
  const count3 = res.data?.count ?? rows.length;
35080
35205
  if (!count3) {
35081
35206
  lines.push(`- [ok] issue.delete_where: 0 matches`);
35207
+ results.push({ type: "issue.delete_where", status: "ok", count: 0, keys: [] });
35082
35208
  continue;
35083
35209
  }
35084
35210
  lines.push(`- [ok] issue.delete_where: deleted ${count3} issue(s)`);
35085
35211
  for (const r of rows)
35086
35212
  lines.push(` - ${r.key}`);
35213
+ results.push({ type: "issue.delete_where", status: "ok", count: count3, keys: rows.map((r) => r.key) });
35087
35214
  } else if (a.type === "issue.list") {
35088
35215
  const res = await apiClient.post(queryUrl, {
35089
35216
  kind: "list",
@@ -35094,17 +35221,20 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
35094
35221
  }, { headers });
35095
35222
  if (!res.success) {
35096
35223
  lines.push(`- [err] issue.list: ${res.error || res.status}`);
35224
+ results.push({ type: "issue.list", status: "error", error: String(res.error || res.status) });
35097
35225
  continue;
35098
35226
  }
35099
35227
  const rows = Array.isArray(res.data) ? res.data : Array.isArray(res.data?.results) ? res.data.results : [];
35100
35228
  if (!rows.length) {
35101
35229
  lines.push(`- [ok] issue.list: 0 issues`);
35230
+ results.push({ type: "issue.list", status: "ok", count: 0 });
35102
35231
  continue;
35103
35232
  }
35104
35233
  lines.push(`- [ok] issue.list: ${rows.length} issue(s)`);
35105
35234
  for (const r of rows) {
35106
35235
  lines.push(` - **${r.key}** [${r.status}] ${r.title}${r.assignee_id ? ` (@${r.assignee_id})` : ""}`);
35107
35236
  }
35237
+ results.push({ type: "issue.list", status: "ok", count: rows.length });
35108
35238
  } else if (a.type === "issue.search") {
35109
35239
  const res = await apiClient.post(queryUrl, {
35110
35240
  kind: "search",
@@ -35114,75 +35244,95 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
35114
35244
  }, { headers });
35115
35245
  if (!res.success) {
35116
35246
  lines.push(`- [err] issue.search "${a.query}": ${res.error || res.status}`);
35247
+ results.push({ type: "issue.search", status: "error", error: String(res.error || res.status), label: a.query });
35117
35248
  continue;
35118
35249
  }
35119
35250
  const rows = Array.isArray(res.data) ? res.data : Array.isArray(res.data?.results) ? res.data.results : [];
35120
35251
  if (!rows.length) {
35121
35252
  lines.push(`- [ok] issue.search "${a.query}": 0 hits`);
35253
+ results.push({ type: "issue.search", status: "ok", count: 0, query: a.query });
35122
35254
  continue;
35123
35255
  }
35124
35256
  lines.push(`- [ok] issue.search "${a.query}": ${rows.length} hit(s)`);
35125
35257
  for (const r of rows) {
35126
35258
  lines.push(` - **${r.key}** [${r.status}] ${r.title}`);
35127
35259
  }
35260
+ results.push({ type: "issue.search", status: "ok", count: rows.length, query: a.query });
35128
35261
  } else if (a.type === "agent.create") {
35129
35262
  if (!parentWsId) {
35130
35263
  lines.push(`- [err] agent.create "${a.name}": no tenant workspace id`);
35264
+ results.push({ type: "agent.create", status: "error", error: "no tenant workspace id", label: a.name });
35131
35265
  continue;
35132
35266
  }
35133
- const res = await apiClient.post(`${apiUrl}/api/workspaces/${parentWsId}/agent_ops/agents/mutate`, { action: "create", name: a.name, type: a.agent_type, prompt: a.prompt, skill_ids: a.skill_ids, allowed_tools: a.allowed_tools, capabilities: a.capabilities }, { headers });
35267
+ const res = await apiClient.post(`${apiUrl}/api/workspaces/${parentWsId}/agent_ops/agents/mutate`, { action: "create", name: a.name, prompt: a.prompt, skill_ids: a.skill_ids, allowed_tools: a.allowed_tools, capabilities: a.capabilities }, { headers });
35134
35268
  if (!res.success) {
35135
35269
  lines.push(`- [err] agent.create "${a.name}": ${res.error || res.status}`);
35270
+ results.push({ type: "agent.create", status: "error", error: String(res.error || res.status), label: a.name });
35136
35271
  continue;
35137
35272
  }
35138
35273
  lines.push(`- [ok] agent.create "${a.name}" -> ${res.data?.agent_id}`);
35274
+ results.push({ type: "agent.create", status: "ok", agent_id: res.data?.agent_id, name: a.name, allowed_tools_count: Array.isArray(a.allowed_tools) ? a.allowed_tools.length : 0, approval_status: "approved" });
35139
35275
  } else if (a.type === "agent.update") {
35140
35276
  if (!parentWsId) {
35141
35277
  lines.push(`- [err] agent.update ${a.id}: no tenant workspace id`);
35278
+ results.push({ type: "agent.update", status: "error", error: "no tenant workspace id", label: a.id });
35142
35279
  continue;
35143
35280
  }
35144
35281
  const res = await apiClient.post(`${apiUrl}/api/workspaces/${parentWsId}/agent_ops/agents/mutate`, { action: "update", ...a }, { headers });
35145
35282
  if (!res.success) {
35146
35283
  lines.push(`- [err] agent.update ${a.id}: ${res.error || res.status}`);
35284
+ results.push({ type: "agent.update", status: "error", error: String(res.error || res.status), label: a.id });
35147
35285
  continue;
35148
35286
  }
35149
- if (res.data?.queued)
35287
+ const changed = ["name", "prompt", "allowed_tools"].filter((k) => a[k] !== undefined);
35288
+ if (res.data?.queued) {
35150
35289
  lines.push(`- [pending] agent.update ${a.id} queued`);
35151
- else
35290
+ results.push({ type: "agent.update", status: "ok", agent_id: a.id, queued: true, pending_op_id: res.data?.pending_op_id, changed });
35291
+ } else {
35152
35292
  lines.push(`- [ok] agent.update ${a.id}`);
35293
+ results.push({ type: "agent.update", status: "ok", agent_id: a.id, queued: false, changed });
35294
+ }
35153
35295
  } else if (a.type === "skill.create") {
35154
35296
  if (!parentWsId) {
35155
35297
  lines.push(`- [err] skill.create "${a.name}": no tenant workspace id`);
35298
+ results.push({ type: "skill.create", status: "error", error: "no tenant workspace id", label: a.name });
35156
35299
  continue;
35157
35300
  }
35158
35301
  const res = await apiClient.post(`${apiUrl}/api/workspaces/${parentWsId}/agent_ops/skills/mutate`, { action: "create", name: a.name, version: a.version, description: a.description, body: a.body, files: a.files }, { headers });
35159
35302
  if (!res.success) {
35160
35303
  lines.push(`- [err] skill.create "${a.name}": ${res.error || res.status}`);
35304
+ results.push({ type: "skill.create", status: "error", error: String(res.error || res.status), label: a.name });
35161
35305
  continue;
35162
35306
  }
35163
35307
  lines.push(`- [pending] skill.create "${a.name}" queued for human review (op ${res.data?.pending_op_id})`);
35308
+ results.push({ type: "skill.create", status: "ok", pending_op_id: res.data?.pending_op_id, name: a.name, description: a.description });
35164
35309
  } else if (a.type === "session.create") {
35165
35310
  if (!parentWsId) {
35166
35311
  lines.push(`- [err] session.create role=${a.role}: no tenant workspace id`);
35312
+ results.push({ type: "session.create", status: "error", error: "no tenant workspace id" });
35167
35313
  continue;
35168
35314
  }
35169
35315
  const res = await apiClient.post(`${apiUrl}/api/workspaces/${parentWsId}/sessions`, { workspace_id: a.workspace_id, agent_id: a.agent_id, role: a.role, device_id: a.device_id }, { headers });
35170
35316
  if (!res.success) {
35171
35317
  lines.push(`- [err] session.create role=${a.role}: ${res.error || res.status}`);
35318
+ results.push({ type: "session.create", status: "error", error: String(res.error || res.status) });
35172
35319
  continue;
35173
35320
  }
35174
35321
  const sess = res.data?.session;
35175
35322
  const reused = res.data?.reused;
35176
35323
  lines.push(`- [${reused ? "reuse" : "ok"}] session.${reused ? "reuse" : "create"} ${sess?.id?.slice(0, 8) || "?"} (role=${a.role})`);
35324
+ results.push({ type: "session.create", status: "ok", session_id: sess?.id, role: a.role, agent_id: a.agent_id, reused: !!reused });
35177
35325
  } else if (a.type === "eval.submit") {
35178
35326
  if (!parentWsId) {
35179
35327
  lines.push(`- [err] eval.submit: no tenant workspace id`);
35328
+ results.push({ type: "eval.submit", status: "error", error: "no tenant workspace id" });
35180
35329
  continue;
35181
35330
  }
35182
35331
  const url2 = `${apiUrl}/api/workspaces/${parentWsId}/agent/issues/eval/submit`;
35183
35332
  const res = await apiClient.post(url2, { id: a.id, score: a.score, feedback: a.feedback, scores: a.scores }, { headers });
35184
35333
  if (!res.success) {
35185
35334
  lines.push(`- [err] eval.submit: ${res.error || res.status}`);
35335
+ results.push({ type: "eval.submit", status: "error", error: String(res.error || res.status) });
35186
35336
  continue;
35187
35337
  }
35188
35338
  const verdict = res.data?.verdict;
@@ -35190,34 +35340,48 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
35190
35340
  const attempt = res.data?.attempt;
35191
35341
  const tag3 = verdict === "pass" ? "ok" : "warn";
35192
35342
  lines.push(`- [${tag3}] eval.submit verdict=${verdict} score=${a.score.toFixed(2)} attempt=${attempt}${action ? ` action=${action}` : ""}`);
35343
+ results.push({ type: "eval.submit", status: "ok", verdict, score: a.score, attempt, action_taken: action });
35193
35344
  } else if (a.type === "skill.attach" || a.type === "skill.detach") {
35194
35345
  const action = a.type === "skill.attach" ? "attach_skill" : "detach_skill";
35195
35346
  if (!parentWsId) {
35196
35347
  lines.push(`- [err] ${a.type}: no tenant workspace id`);
35348
+ results.push({ type: a.type, status: "error", error: "no tenant workspace id" });
35197
35349
  continue;
35198
35350
  }
35199
35351
  const res = await apiClient.post(`${apiUrl}/api/workspaces/${parentWsId}/agent_ops/agents/mutate`, { action, agent_id: a.agent_id, skill_id: a.skill_id }, { headers });
35200
35352
  if (!res.success) {
35201
35353
  lines.push(`- [err] ${a.type} ${a.skill_id}->${a.agent_id}: ${res.error || res.status}`);
35354
+ results.push({ type: a.type, status: "error", error: String(res.error || res.status), label: `${a.skill_id}->${a.agent_id}` });
35202
35355
  continue;
35203
35356
  }
35204
- if (res.data?.queued)
35357
+ if (res.data?.queued) {
35205
35358
  lines.push(`- [pending] ${a.type} queued`);
35206
- else
35359
+ results.push({ type: a.type, status: "ok", agent_id: a.agent_id, skill_id: a.skill_id, queued: true, pending_op_id: res.data?.pending_op_id });
35360
+ } else {
35207
35361
  lines.push(`- [ok] ${a.type} ${a.skill_id} <-> ${a.agent_id}`);
35362
+ results.push({ type: a.type, status: "ok", agent_id: a.agent_id, skill_id: a.skill_id, queued: false });
35363
+ }
35208
35364
  }
35209
35365
  } catch (e) {
35210
35366
  lines.push(`- [err] ${a.type} failed: ${String(e)}`);
35367
+ results.push({ type: a.type, status: "error", error: String(e) });
35211
35368
  }
35212
35369
  }
35213
- if (truncated)
35370
+ if (truncated) {
35214
35371
  lines.push(`- [warn] action list truncated at ${PLAN_ACTION_LIMIT}`);
35372
+ results.push({ type: "note", status: "note", message: `Action list truncated at ${PLAN_ACTION_LIMIT}` });
35373
+ }
35215
35374
  if (!lines.length)
35216
35375
  return "";
35376
+ const json2 = JSON.stringify({ version: 1, actions: results });
35217
35377
  return `**Planning actions**
35218
35378
 
35219
35379
  ${lines.join(`
35220
- `)}`;
35380
+ `)}
35381
+
35382
+ \`\`\`multi-actions
35383
+ ${json2}
35384
+ \`\`\``;
35221
35385
  }
35222
35386
  function stripMd(s) {
35223
35387
  return s.replace(/[`*_]/g, "").trim();
@@ -35822,6 +35986,22 @@ class ChatPeer {
35822
35986
  getDoc() {
35823
35987
  return this.doc;
35824
35988
  }
35989
+ findMessage(id3) {
35990
+ for (const m of listMessages(this.doc))
35991
+ if (m.id === id3)
35992
+ return m;
35993
+ return null;
35994
+ }
35995
+ async awaitMessage(id3, timeoutMs = 5000) {
35996
+ const start3 = Date.now();
35997
+ while (Date.now() - start3 < timeoutMs) {
35998
+ const found = this.findMessage(id3);
35999
+ if (found)
36000
+ return found;
36001
+ await new Promise((r) => setTimeout(r, 50));
36002
+ }
36003
+ return null;
36004
+ }
35825
36005
  start() {
35826
36006
  this.connect();
35827
36007
  }
@@ -35898,8 +36078,9 @@ class ChatPeer {
35898
36078
  if (before2.has(m.id))
35899
36079
  continue;
35900
36080
  this.seenIds.add(m.id);
35901
- if (m.author?.kind === "user" && !m.partial) {
35902
- Promise.resolve().then(() => this.opts.onUserMessage(m, this)).catch((e) => this.opts.log(`[chat ${this.chatId}] onUserMessage error: ${e.message}`));
36081
+ if (m.author?.kind === "user" && !m.partial && this.opts.onUserMessage) {
36082
+ const cb = this.opts.onUserMessage;
36083
+ Promise.resolve().then(() => cb(m, this)).catch((e) => this.opts.log(`[chat ${this.chatId}] onUserMessage error: ${e.message}`));
35903
36084
  }
35904
36085
  }
35905
36086
  this.dirtySinceWrite++;
@@ -36046,8 +36227,11 @@ var init_chat_turn = __esm(() => {
36046
36227
  // src/_impl/chat-plan-actions.ts
36047
36228
  async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36048
36229
  const lines = [];
36049
- for (const e of parseErrors)
36230
+ const results = [];
36231
+ for (const e of parseErrors) {
36050
36232
  lines.push(`- [err] plan parse: ${e.message}`);
36233
+ results.push({ type: "parse", status: "error", error: e.message });
36234
+ }
36051
36235
  let actions = actionsIn;
36052
36236
  let truncated = false;
36053
36237
  if (actions.length > PLAN_ACTION_LIMIT2) {
@@ -36062,6 +36246,7 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36062
36246
  counts[a.type] = (counts[a.type] || 0) + 1;
36063
36247
  if (counts[a.type] > cap) {
36064
36248
  lines.push(`- [warn] ${a.type} sub-cap ${cap} hit, dropping extra`);
36249
+ results.push({ type: a.type, status: "note", message: `${a.type} sub-cap ${cap} hit, dropping extra` });
36065
36250
  return false;
36066
36251
  }
36067
36252
  return true;
@@ -36092,6 +36277,7 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36092
36277
  const project_id = a.project_id || ctx.projectId;
36093
36278
  if (!project_id) {
36094
36279
  lines.push(`- [err] create "${a.title}": project_id required (chat has no pinned project)`);
36280
+ results.push({ type: "create", status: "error", error: "project_id required (chat has no pinned project)", label: a.title });
36095
36281
  tally(false);
36096
36282
  continue;
36097
36283
  }
@@ -36103,34 +36289,41 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36103
36289
  priority: a.priority,
36104
36290
  assignee_type: a.assignee_type,
36105
36291
  assignee_id: a.assignee_id,
36106
- parent_id: a.parent_id
36292
+ parent_id: a.parent_id,
36293
+ await_children: a.await_children
36107
36294
  };
36108
36295
  const res = await apiClient.post(mutateUrl, body, { headers });
36109
36296
  if (!res.success) {
36110
36297
  lines.push(`- [err] create "${a.title}": ${res.error || res.status}`);
36298
+ results.push({ type: "create", status: "error", error: String(res.error || res.status), label: a.title });
36111
36299
  tally(false);
36112
36300
  continue;
36113
36301
  }
36114
36302
  const created = res.data;
36115
36303
  lines.push(`- [ok] created **${created.key}** - ${created.title}${created.assignee_id ? ` -> @${created.assignee_id}` : ""}`);
36304
+ results.push({ type: "create", status: "ok", issue_id: created.id, key: created.key, title: created.title, assignee_id: created.assignee_id ?? null, assignee_type: created.assignee_type ?? null, priority: created.priority ?? null, autonomy_level: created.autonomy_level || "auto" });
36116
36305
  tally(true);
36117
36306
  } else if (a.type === "update") {
36118
36307
  const res = await apiClient.post(mutateUrl, { action: "update", ...a }, { headers });
36119
36308
  if (!res.success) {
36120
36309
  lines.push(`- [err] update ${a.id}: ${res.error || res.status}`);
36310
+ results.push({ type: "update", status: "error", error: String(res.error || res.status), label: a.id });
36121
36311
  tally(false);
36122
36312
  continue;
36123
36313
  }
36124
36314
  lines.push(`- [ok] updated ${res.data.key}`);
36315
+ results.push({ type: "update", status: "ok", issue_id: res.data.id, key: res.data.key, status_to: a.status ?? null, title_to: a.title ?? null });
36125
36316
  tally(true);
36126
36317
  } else if (a.type === "delegate") {
36127
36318
  const res = await apiClient.post(mutateUrl, { action: "update", id: a.id, assignee_type: "agent", assignee_id: a.assignee_id, status: "todo" }, { headers });
36128
36319
  if (!res.success) {
36129
36320
  lines.push(`- [err] delegate ${a.id}: ${res.error || res.status}`);
36321
+ results.push({ type: "delegate", status: "error", error: String(res.error || res.status), label: a.id });
36130
36322
  tally(false);
36131
36323
  continue;
36132
36324
  }
36133
36325
  lines.push(`- [ok] delegated ${res.data.key} -> ${a.assignee_id}`);
36326
+ results.push({ type: "delegate", status: "ok", issue_id: res.data.id, key: res.data.key, assignee_id: a.assignee_id });
36134
36327
  tally(true);
36135
36328
  } else if (a.type === "handoff") {
36136
36329
  const res = await apiClient.post(mutateUrl, {
@@ -36143,40 +36336,48 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36143
36336
  }, { headers });
36144
36337
  if (!res.success) {
36145
36338
  lines.push(`- [err] handoff -> ${a.target_agent_id}: ${res.error || res.status}`);
36339
+ results.push({ type: "handoff", status: "error", error: String(res.error || res.status), label: a.target_agent_id });
36146
36340
  tally(false);
36147
36341
  continue;
36148
36342
  }
36149
36343
  lines.push(`- [ok] handoff -> ${a.target_agent_id} via **${res.data?.child_key}** expect=${a.expect ?? "summary"}`);
36344
+ results.push({ type: "handoff", status: "ok", issue_id: res.data?.child_id, key: res.data?.child_key, assignee_id: a.target_agent_id, expect: a.expect ?? "summary" });
36150
36345
  tally(true);
36151
36346
  } else if (a.type === "issue.comment") {
36152
36347
  const res = await apiClient.post(mutateUrl, { action: "comment", id: a.id, body: a.body }, { headers });
36153
36348
  if (!res.success) {
36154
36349
  lines.push(`- [err] issue.comment ${a.id}: ${res.error || res.status}`);
36350
+ results.push({ type: "issue.comment", status: "error", error: String(res.error || res.status), label: a.id });
36155
36351
  tally(false);
36156
36352
  continue;
36157
36353
  }
36158
36354
  const dispatched = res.data?.dispatched;
36159
36355
  lines.push(`- [ok] commented on ${a.id}${dispatched ? " -> dispatched agent" : ""}`);
36356
+ results.push({ type: "issue.comment", status: "ok", issue_id: a.id, dispatched: !!dispatched });
36160
36357
  tally(true);
36161
36358
  } else if (a.type === "issue.delete") {
36162
36359
  const res = await apiClient.post(mutateUrl, { action: "delete", id: a.id }, { headers });
36163
36360
  if (!res.success) {
36164
36361
  lines.push(`- [err] issue.delete ${a.id}: ${res.error || res.status}`);
36362
+ results.push({ type: "issue.delete", status: "error", error: String(res.error || res.status), label: a.id });
36165
36363
  tally(false);
36166
36364
  continue;
36167
36365
  }
36168
36366
  lines.push(`- [ok] deleted ${res.data?.key || a.id}`);
36367
+ results.push({ type: "issue.delete", status: "ok", key: res.data?.key || a.id, issue_id: res.data?.id || a.id });
36169
36368
  tally(true);
36170
36369
  } else if (a.type === "issue.delete_where") {
36171
36370
  const project_id = a.project_id || ctx.projectId;
36172
36371
  if (!project_id) {
36173
36372
  lines.push(`- [err] issue.delete_where: project_id required`);
36373
+ results.push({ type: "issue.delete_where", status: "error", error: "project_id required" });
36174
36374
  tally(false);
36175
36375
  continue;
36176
36376
  }
36177
36377
  const res = await apiClient.post(mutateUrl, { action: "delete_where", project_id, status: a.status, assignee_id: a.assignee_id, limit: a.limit ?? 50 }, { headers });
36178
36378
  if (!res.success) {
36179
36379
  lines.push(`- [err] issue.delete_where: ${res.error || res.status}`);
36380
+ results.push({ type: "issue.delete_where", status: "error", error: String(res.error || res.status) });
36180
36381
  tally(false);
36181
36382
  continue;
36182
36383
  }
@@ -36185,18 +36386,21 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36185
36386
  lines.push(`- [ok] issue.delete_where: deleted ${count3} issue(s)`);
36186
36387
  for (const r of rows)
36187
36388
  lines.push(` - ${r.key}`);
36389
+ results.push({ type: "issue.delete_where", status: "ok", count: count3, keys: rows.map((r) => r.key) });
36188
36390
  tally(true);
36189
36391
  } else if (a.type === "issue.list") {
36190
36392
  const project_id = a.project_id || ctx.projectId;
36191
36393
  const res = await apiClient.post(queryUrl, { kind: "list", project_id, status: a.status, assignee_id: a.assignee_id, limit: a.limit ?? 20 }, { headers });
36192
36394
  if (!res.success) {
36193
36395
  lines.push(`- [err] issue.list: ${res.error || res.status}`);
36396
+ results.push({ type: "issue.list", status: "error", error: String(res.error || res.status) });
36194
36397
  tally(false);
36195
36398
  continue;
36196
36399
  }
36197
36400
  const rows = Array.isArray(res.data) ? res.data : Array.isArray(res.data?.results) ? res.data.results : [];
36198
36401
  if (!rows.length) {
36199
36402
  lines.push(`- [ok] issue.list: 0 issues`);
36403
+ results.push({ type: "issue.list", status: "ok", count: 0 });
36200
36404
  tally(true);
36201
36405
  continue;
36202
36406
  }
@@ -36204,12 +36408,14 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36204
36408
  for (const r of rows) {
36205
36409
  lines.push(` - **${r.key}** [${r.status}] ${r.title}${r.assignee_id ? ` (@${r.assignee_id})` : ""}`);
36206
36410
  }
36411
+ results.push({ type: "issue.list", status: "ok", count: rows.length });
36207
36412
  tally(true);
36208
36413
  } else if (a.type === "issue.search") {
36209
36414
  const project_id = a.project_id || ctx.projectId;
36210
36415
  const res = await apiClient.post(queryUrl, { kind: "search", project_id, query: a.query, limit: a.limit ?? 10 }, { headers });
36211
36416
  if (!res.success) {
36212
36417
  lines.push(`- [err] issue.search "${a.query}": ${res.error || res.status}`);
36418
+ results.push({ type: "issue.search", status: "error", error: String(res.error || res.status), label: a.query });
36213
36419
  tally(false);
36214
36420
  continue;
36215
36421
  }
@@ -36217,12 +36423,12 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36217
36423
  lines.push(`- [ok] issue.search "${a.query}": ${rows.length} hit(s)`);
36218
36424
  for (const r of rows)
36219
36425
  lines.push(` - **${r.key}** [${r.status}] ${r.title}`);
36426
+ results.push({ type: "issue.search", status: "ok", count: rows.length, query: a.query });
36220
36427
  tally(true);
36221
36428
  } else if (a.type === "agent.create") {
36222
36429
  const res = await apiClient.post(agentsMutateUrl, {
36223
36430
  action: "create",
36224
36431
  name: a.name,
36225
- type: a.agent_type,
36226
36432
  prompt: a.prompt,
36227
36433
  skill_ids: a.skill_ids,
36228
36434
  allowed_tools: a.allowed_tools,
@@ -36230,19 +36436,24 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36230
36436
  }, { headers });
36231
36437
  if (!res.success) {
36232
36438
  lines.push(`- [err] agent.create "${a.name}": ${res.error || res.status}`);
36439
+ results.push({ type: "agent.create", status: "error", error: String(res.error || res.status), label: a.name });
36233
36440
  tally(false);
36234
36441
  continue;
36235
36442
  }
36236
36443
  lines.push(`- [ok] agent.create "${a.name}" -> ${res.data?.agent_id}`);
36444
+ results.push({ type: "agent.create", status: "ok", agent_id: res.data?.agent_id, name: a.name, allowed_tools_count: Array.isArray(a.allowed_tools) ? a.allowed_tools.length : 0, approval_status: "approved" });
36237
36445
  tally(true);
36238
36446
  } else if (a.type === "agent.update") {
36239
36447
  const res = await apiClient.post(agentsMutateUrl, { action: "update", ...a }, { headers });
36240
36448
  if (!res.success) {
36241
36449
  lines.push(`- [err] agent.update ${a.id}: ${res.error || res.status}`);
36450
+ results.push({ type: "agent.update", status: "error", error: String(res.error || res.status), label: a.id });
36242
36451
  tally(false);
36243
36452
  continue;
36244
36453
  }
36454
+ const changed = ["name", "prompt", "allowed_tools"].filter((k) => a[k] !== undefined);
36245
36455
  lines.push(res.data?.queued ? `- [pending] agent.update ${a.id} queued` : `- [ok] agent.update ${a.id}`);
36456
+ results.push({ type: "agent.update", status: "ok", agent_id: a.id, queued: !!res.data?.queued, pending_op_id: res.data?.pending_op_id, changed });
36246
36457
  tally(true);
36247
36458
  } else if (a.type === "skill.create") {
36248
36459
  const res = await apiClient.post(skillsMutateUrl, {
@@ -36255,53 +36466,68 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36255
36466
  }, { headers });
36256
36467
  if (!res.success) {
36257
36468
  lines.push(`- [err] skill.create "${a.name}": ${res.error || res.status}`);
36469
+ results.push({ type: "skill.create", status: "error", error: String(res.error || res.status), label: a.name });
36258
36470
  tally(false);
36259
36471
  continue;
36260
36472
  }
36261
36473
  const queued = res.data?.pending_op_id;
36262
36474
  lines.push(queued ? `- [pending] skill.create "${a.name}" queued for human review (op ${queued})` : `- [ok] skill.create "${a.name}" -> ${res.data?.skill_id}`);
36475
+ results.push({ type: "skill.create", status: "ok", pending_op_id: queued, name: a.name, description: a.description });
36263
36476
  tally(true);
36264
36477
  } else if (a.type === "skill.attach" || a.type === "skill.detach") {
36265
36478
  const action = a.type === "skill.attach" ? "attach_skill" : "detach_skill";
36266
36479
  const res = await apiClient.post(agentsMutateUrl, { action, agent_id: a.agent_id, skill_id: a.skill_id }, { headers });
36267
36480
  if (!res.success) {
36268
36481
  lines.push(`- [err] ${a.type} ${a.skill_id}->${a.agent_id}: ${res.error || res.status}`);
36482
+ results.push({ type: a.type, status: "error", error: String(res.error || res.status), label: `${a.skill_id}->${a.agent_id}` });
36269
36483
  tally(false);
36270
36484
  continue;
36271
36485
  }
36272
36486
  lines.push(res.data?.queued ? `- [pending] ${a.type} queued` : `- [ok] ${a.type} ${a.skill_id} <-> ${a.agent_id}`);
36487
+ results.push({ type: a.type, status: "ok", agent_id: a.agent_id, skill_id: a.skill_id, queued: !!res.data?.queued, pending_op_id: res.data?.pending_op_id });
36273
36488
  tally(true);
36274
36489
  } else if (a.type === "session.create") {
36275
36490
  const res = await apiClient.post(sessionsUrl, { workspace_id: a.workspace_id, agent_id: a.agent_id, role: a.role, device_id: a.device_id }, { headers });
36276
36491
  if (!res.success) {
36277
36492
  lines.push(`- [err] session.create role=${a.role}: ${res.error || res.status}`);
36493
+ results.push({ type: "session.create", status: "error", error: String(res.error || res.status) });
36278
36494
  tally(false);
36279
36495
  continue;
36280
36496
  }
36281
36497
  const sess = res.data?.session;
36282
36498
  const reused = res.data?.reused;
36283
36499
  lines.push(`- [${reused ? "reuse" : "ok"}] session.${reused ? "reuse" : "create"} ${sess?.id?.slice(0, 8) || "?"} (role=${a.role})`);
36500
+ results.push({ type: "session.create", status: "ok", session_id: sess?.id, role: a.role, agent_id: a.agent_id, reused: !!reused });
36284
36501
  tally(true);
36285
36502
  } else if (a.type === "eval.submit") {
36286
36503
  lines.push(`- [err] eval.submit not allowed in chat (only inside an eval dispatch)`);
36504
+ results.push({ type: "eval.submit", status: "error", error: "eval.submit not allowed in chat" });
36287
36505
  tally(false);
36288
36506
  }
36289
36507
  } catch (e) {
36290
36508
  lines.push(`- [err] ${a.type} failed: ${String(e)}`);
36509
+ results.push({ type: a.type, status: "error", error: String(e) });
36291
36510
  tally(false);
36292
36511
  }
36293
36512
  }
36294
- if (truncated)
36513
+ if (truncated) {
36295
36514
  lines.push(`- [warn] action list truncated at ${PLAN_ACTION_LIMIT2}`);
36296
- return { ok, fail: fail12, lines };
36515
+ results.push({ type: "note", status: "note", message: `Action list truncated at ${PLAN_ACTION_LIMIT2}` });
36516
+ }
36517
+ return { ok, fail: fail12, lines, results };
36297
36518
  }
36298
36519
  function renderChatPlanSummary(res) {
36299
36520
  if (!res.lines.length)
36300
36521
  return "";
36522
+ const json2 = JSON.stringify({ version: 1, actions: res.results });
36301
36523
  return `**Planning actions**
36302
36524
 
36303
36525
  ${res.lines.join(`
36304
- `)}`;
36526
+ `)}
36527
+
36528
+ \`\`\`multi-actions
36529
+ ${json2}
36530
+ \`\`\``;
36305
36531
  }
36306
36532
  var PLAN_ACTION_LIMIT2 = 10, SUBCAPS;
36307
36533
  var init_chat_plan_actions = __esm(() => {
@@ -36321,391 +36547,327 @@ var init_chat_plan_actions = __esm(() => {
36321
36547
  // src/_impl/chat-supervisor.ts
36322
36548
  var exports_chat_supervisor = {};
36323
36549
  __export(exports_chat_supervisor, {
36324
- ChatSupervisor: () => ChatSupervisor
36325
- });
36326
-
36327
- class ChatSupervisor {
36328
- opts;
36329
- peers = new Map;
36330
- chats = new Map;
36331
- timer = null;
36332
- closed = false;
36333
- constructor(opts) {
36334
- this.opts = opts;
36335
- }
36336
- start() {
36337
- this.refresh().catch((e) => this.opts.log(`[chats] refresh err: ${e.message}`));
36338
- this.timer = setInterval(() => {
36339
- this.refresh().catch((e) => this.opts.log(`[chats] refresh err: ${e.message}`));
36340
- }, 30000);
36341
- }
36342
- close() {
36343
- this.closed = true;
36344
- if (this.timer) {
36345
- clearInterval(this.timer);
36346
- this.timer = null;
36347
- }
36348
- for (const p of this.peers.values())
36349
- p.close();
36350
- this.peers.clear();
36550
+ runChatTurn: () => runChatTurn
36551
+ });
36552
+ async function runChatTurn(opts) {
36553
+ const { apiUrl, authToken: authToken2, workspaceId, chatId, messageId, log: log4 } = opts;
36554
+ const headers = { authorization: `Bearer ${authToken2}` };
36555
+ const metaR = await fetch(`${apiUrl}/api/workspaces/${workspaceId}/chats/${chatId}`, { headers });
36556
+ if (!metaR.ok) {
36557
+ log4(`[chat ${chatId}] meta fetch failed: ${metaR.status}`);
36558
+ return;
36351
36559
  }
36352
- async refresh() {
36353
- if (this.closed)
36560
+ const chat2 = await metaR.json();
36561
+ const peer = new ChatPeer({
36562
+ apiUrl,
36563
+ authToken: authToken2,
36564
+ workspaceId,
36565
+ chatId,
36566
+ primaryAgentId: chat2.primary_agent_id,
36567
+ log: log4
36568
+ });
36569
+ peer.start();
36570
+ try {
36571
+ const msg = await peer.awaitMessage(messageId, 7000);
36572
+ if (!msg) {
36573
+ log4(`[chat ${chatId}] message ${messageId} not visible after sync; aborting`);
36354
36574
  return;
36355
- const all8 = await this.listAllChats();
36356
- const mine = all8.filter((c) => c.device_id && c.device_id === this.opts.deviceId);
36357
- const want = new Set;
36358
- for (const c of mine) {
36359
- want.add(c.id);
36360
- this.chats.set(c.id, c);
36361
- if (!this.peers.has(c.id)) {
36362
- const peer = new ChatPeer({
36363
- apiUrl: this.opts.apiUrl,
36364
- authToken: this.opts.authToken,
36365
- workspaceId: this.opts.workspaceId,
36366
- chatId: c.id,
36367
- primaryAgentId: c.primary_agent_id,
36368
- log: this.opts.log,
36369
- onUserMessage: async (msg, p) => this.onUserMessage(c, msg, p)
36370
- });
36371
- peer.start();
36372
- this.peers.set(c.id, peer);
36373
- this.opts.log(`[chats] watching ${c.id} (${c.title})`);
36374
- }
36375
- }
36376
- for (const [id3, peer] of this.peers) {
36377
- if (!want.has(id3)) {
36378
- peer.close();
36379
- this.peers.delete(id3);
36380
- this.chats.delete(id3);
36381
- this.opts.log(`[chats] released ${id3}`);
36382
- }
36383
36575
  }
36576
+ await processUserMessage(chat2, msg, peer, { apiUrl, authToken: authToken2, workspaceId, log: log4 });
36577
+ } finally {
36578
+ peer.close();
36384
36579
  }
36385
- async listAllChats() {
36386
- const headers = { authorization: `Bearer ${this.opts.authToken}` };
36387
- const out = [];
36388
- try {
36389
- const r = await fetch(`${this.opts.apiUrl}/api/workspaces/${this.opts.workspaceId}/chats`, { headers });
36390
- if (r.ok) {
36391
- const body = await r.json();
36392
- for (const x of body.results)
36393
- out.push(x);
36394
- }
36395
- } catch {}
36396
- try {
36397
- const pr = await fetch(`${this.opts.apiUrl}/api/workspaces/${this.opts.workspaceId}/devices/me/projects`, { headers });
36398
- if (pr.ok) {
36399
- const pbody = await pr.json();
36400
- for (const pid of pbody.project_ids) {
36401
- try {
36402
- const cr = await fetch(`${this.opts.apiUrl}/api/workspaces/${this.opts.workspaceId}/projects/${pid}/chats`, { headers });
36403
- if (cr.ok) {
36404
- const cbody = await cr.json();
36405
- for (const x of cbody.results)
36406
- out.push(x);
36407
- }
36408
- } catch {}
36409
- }
36410
- }
36411
- } catch {}
36412
- return out;
36580
+ }
36581
+ async function fetchAgents(apiUrl, workspaceId, authToken2) {
36582
+ const headers = { authorization: `Bearer ${authToken2}` };
36583
+ try {
36584
+ const r = await fetch(`${apiUrl}/api/workspaces/${workspaceId}/agents`, { headers });
36585
+ if (!r.ok)
36586
+ return [];
36587
+ const body = await r.json();
36588
+ const arr = Array.isArray(body) ? body : Array.isArray(body.results) ? body.results : [];
36589
+ return arr.map((a) => ({ id: a.id, name: a.name }));
36590
+ } catch {
36591
+ return [];
36413
36592
  }
36414
- async resolveCwd(chat2) {
36415
- if (!chat2.project_id || !chat2.device_id)
36416
- return;
36417
- const headers = { authorization: `Bearer ${this.opts.authToken}` };
36418
- try {
36419
- const r = await fetch(`${this.opts.apiUrl}/api/workspaces/${this.opts.workspaceId}/projects/${chat2.project_id}/devices`, { headers });
36420
- if (!r.ok)
36421
- return;
36422
- const body = await r.json();
36423
- const row = body.results.find((d) => d.id === chat2.device_id);
36424
- return row?.working_dir || undefined;
36425
- } catch {
36593
+ }
36594
+ function resolveAgentFromMention(agents, mentioned) {
36595
+ if (!mentioned)
36596
+ return null;
36597
+ const m = mentioned.toLowerCase();
36598
+ return agents.find((a) => a.name?.toLowerCase() === m || a.id === mentioned) ?? null;
36599
+ }
36600
+ async function resolveCwd(apiUrl, workspaceId, authToken2, chat2) {
36601
+ if (!chat2.project_id || !chat2.device_id)
36602
+ return;
36603
+ const headers = { authorization: `Bearer ${authToken2}` };
36604
+ try {
36605
+ const r = await fetch(`${apiUrl}/api/workspaces/${workspaceId}/projects/${chat2.project_id}/devices`, { headers });
36606
+ if (!r.ok)
36426
36607
  return;
36427
- }
36608
+ const body = await r.json();
36609
+ const row = body.results.find((d) => d.id === chat2.device_id);
36610
+ return row?.working_dir || undefined;
36611
+ } catch {
36612
+ return;
36428
36613
  }
36429
- async resolveAgentRuntime(chat2, agentId) {
36430
- if (chat2.runtime)
36431
- return chat2.runtime;
36432
- if (!agentId || typeof agentId !== "string")
36433
- return null;
36434
- const headers = { authorization: `Bearer ${this.opts.authToken}` };
36435
- try {
36436
- const r = await fetch(`${this.opts.apiUrl}/api/workspaces/${this.opts.workspaceId}/agents/${agentId}`, { headers });
36437
- if (!r.ok)
36438
- return null;
36439
- const body = await r.json();
36440
- return body.runtime || body.type || null;
36441
- } catch {
36614
+ }
36615
+ async function resolveAgentRuntime(apiUrl, workspaceId, authToken2, chat2, agentId) {
36616
+ if (chat2.runtime)
36617
+ return chat2.runtime;
36618
+ if (!agentId || typeof agentId !== "string")
36619
+ return null;
36620
+ const headers = { authorization: `Bearer ${authToken2}` };
36621
+ try {
36622
+ const r = await fetch(`${apiUrl}/api/workspaces/${workspaceId}/agents/${agentId}`, { headers });
36623
+ if (!r.ok)
36442
36624
  return null;
36443
- }
36625
+ const body = await r.json();
36626
+ return body.runtime || body.type || null;
36627
+ } catch {
36628
+ return null;
36444
36629
  }
36445
- async fetchAgents() {
36446
- const headers = { authorization: `Bearer ${this.opts.authToken}` };
36447
- try {
36448
- const r = await fetch(`${this.opts.apiUrl}/api/workspaces/${this.opts.workspaceId}/agents`, { headers });
36449
- if (!r.ok)
36450
- return [];
36451
- const body = await r.json();
36452
- const arr = Array.isArray(body) ? body : Array.isArray(body.results) ? body.results : [];
36453
- return arr.map((a) => ({ id: a.id, name: a.name }));
36454
- } catch {
36455
- return [];
36630
+ }
36631
+ async function processUserMessage(chat2, userMsg, peer, ctx) {
36632
+ const { apiUrl, authToken: authToken2, workspaceId, log: log4 } = ctx;
36633
+ log4(`[chat ${chat2.id}] user: ${userMsg.text.slice(0, 80)}`);
36634
+ const allAgents = await fetchAgents(apiUrl, workspaceId, authToken2);
36635
+ const mentioned = userMsg.mentions?.[0];
36636
+ const mentionAgent = resolveAgentFromMention(allAgents, mentioned);
36637
+ const resolvedAgentId = mentionAgent?.id ?? chat2.primary_agent_id ?? null;
36638
+ const agentAuthorId = resolvedAgentId || mentioned || "agent";
36639
+ const runtime4 = await resolveAgentRuntime(apiUrl, workspaceId, authToken2, chat2, resolvedAgentId);
36640
+ const cwd = await resolveCwd(apiUrl, workspaceId, authToken2, chat2);
36641
+ if (cwd)
36642
+ log4(`[chat ${chat2.id}] cwd=${cwd}`);
36643
+ if (mentionAgent)
36644
+ log4(`[chat ${chat2.id}] mention → ${mentionAgent.name}`);
36645
+ let textContainerId = null;
36646
+ let buffered = "";
36647
+ let lastFlush = Date.now();
36648
+ let agentReplyText = "";
36649
+ const toolMsgByCallId = new Map;
36650
+ const flushText = (force = false) => {
36651
+ const now = Date.now();
36652
+ if (!buffered || textContainerId == null)
36653
+ return;
36654
+ if (force || now - lastFlush > 200) {
36655
+ peer.appendPartialText(textContainerId, buffered);
36656
+ buffered = "";
36657
+ lastFlush = now;
36456
36658
  }
36457
- }
36458
- resolveAgentFromMention(agents, mentioned) {
36459
- if (!mentioned)
36460
- return null;
36461
- const m = mentioned.toLowerCase();
36462
- return agents.find((a) => a.name?.toLowerCase() === m || a.id === mentioned) ?? null;
36463
- }
36464
- async onUserMessage(chat2, userMsg, peer) {
36465
- this.opts.log(`[chat ${chat2.id}] user: ${userMsg.text.slice(0, 80)}`);
36466
- const allAgents = await this.fetchAgents();
36467
- const mentioned = userMsg.mentions?.[0];
36468
- const mentionAgent = this.resolveAgentFromMention(allAgents, mentioned);
36469
- const resolvedAgentId = mentionAgent?.id ?? chat2.primary_agent_id ?? null;
36470
- const agentAuthorId = resolvedAgentId || mentioned || "agent";
36471
- const runtime4 = await this.resolveAgentRuntime(chat2, resolvedAgentId);
36472
- const cwd = await this.resolveCwd(chat2);
36473
- if (cwd)
36474
- this.opts.log(`[chat ${chat2.id}] cwd=${cwd}`);
36475
- if (mentionAgent)
36476
- this.opts.log(`[chat ${chat2.id}] mention → ${mentionAgent.name}`);
36477
- let textContainerId = null;
36478
- let buffered = "";
36479
- let lastFlush = Date.now();
36480
- let agentReplyText = "";
36481
- const toolMsgByCallId = new Map;
36482
- const flushText = (force = false) => {
36483
- const now = Date.now();
36484
- if (!buffered || textContainerId == null)
36485
- return;
36486
- if (force || now - lastFlush > 200) {
36487
- peer.appendPartialText(textContainerId, buffered);
36488
- buffered = "";
36489
- lastFlush = now;
36490
- }
36491
- };
36492
- const closeText = () => {
36493
- if (textContainerId == null)
36494
- return;
36495
- flushText(true);
36496
- peer.finalizePartialMessage(textContainerId);
36497
- textContainerId = null;
36498
- };
36499
- const agentDisplayName = mentionAgent?.name ?? allAgents.find((a) => a.id === resolvedAgentId)?.name ?? runtime4 ?? undefined;
36500
- const ensureTextOpen = () => {
36501
- if (textContainerId == null) {
36502
- textContainerId = peer.beginPartialAgentMessage(agentAuthorId, agentDisplayName).containerId;
36503
- }
36504
- };
36505
- const summarizeInput = (raw) => {
36506
- if (!raw || typeof raw !== "object")
36507
- return;
36508
- const r = raw;
36509
- const out = {};
36510
- if (typeof r.file_path === "string")
36511
- out.file_path = r.file_path;
36512
- if (typeof r.path === "string")
36513
- out.path = r.path;
36514
- if (typeof r.filename === "string")
36515
- out.filename = r.filename;
36516
- if (typeof r.command === "string")
36517
- out.command = String(r.command).slice(0, 400);
36518
- if (typeof r.pattern === "string")
36519
- out.pattern = String(r.pattern).slice(0, 200);
36520
- if (typeof r.query === "string")
36521
- out.query = String(r.query).slice(0, 200);
36522
- if (typeof r.url === "string")
36523
- out.url = r.url;
36524
- return Object.keys(out).length ? out : undefined;
36525
- };
36526
- const summarizeContent = (raw) => {
36527
- const bytes = raw?.length ?? 0;
36528
- const lines = raw ? raw.split(`
36659
+ };
36660
+ const closeText = () => {
36661
+ if (textContainerId == null)
36662
+ return;
36663
+ flushText(true);
36664
+ peer.finalizePartialMessage(textContainerId);
36665
+ textContainerId = null;
36666
+ };
36667
+ const agentDisplayName = mentionAgent?.name ?? allAgents.find((a) => a.id === resolvedAgentId)?.name ?? runtime4 ?? undefined;
36668
+ const ensureTextOpen = () => {
36669
+ if (textContainerId == null) {
36670
+ textContainerId = peer.beginPartialAgentMessage(agentAuthorId, agentDisplayName).containerId;
36671
+ }
36672
+ };
36673
+ const summarizeInput = (raw) => {
36674
+ if (!raw || typeof raw !== "object")
36675
+ return;
36676
+ const r = raw;
36677
+ const out = {};
36678
+ if (typeof r.file_path === "string")
36679
+ out.file_path = r.file_path;
36680
+ if (typeof r.path === "string")
36681
+ out.path = r.path;
36682
+ if (typeof r.filename === "string")
36683
+ out.filename = r.filename;
36684
+ if (typeof r.command === "string")
36685
+ out.command = String(r.command).slice(0, 400);
36686
+ if (typeof r.pattern === "string")
36687
+ out.pattern = String(r.pattern).slice(0, 200);
36688
+ if (typeof r.query === "string")
36689
+ out.query = String(r.query).slice(0, 200);
36690
+ if (typeof r.url === "string")
36691
+ out.url = r.url;
36692
+ return Object.keys(out).length ? out : undefined;
36693
+ };
36694
+ const summarizeContent = (raw) => {
36695
+ const bytes = raw?.length ?? 0;
36696
+ const lines = raw ? raw.split(`
36529
36697
  `).length : 0;
36530
- const trimmed = raw && raw.length > 240 ? raw.slice(0, 240) + "…" : raw || "";
36531
- return { content: trimmed, bytes, lines };
36532
- };
36533
- const mapToolKind = (raw) => {
36534
- const s = (raw || "").toLowerCase();
36535
- if (s.includes("edit") || s.includes("modify"))
36536
- return "edit";
36537
- if (s.includes("read") || s.includes("view") || s.includes("fetch"))
36538
- return "read";
36539
- if (s.includes("write") || s.includes("create"))
36540
- return "write";
36541
- if (s.includes("execute") || s.includes("bash") || s.includes("shell") || s.includes("run"))
36542
- return "bash";
36543
- if (s.includes("search") || s.includes("grep") || s.includes("find"))
36544
- return "search";
36545
- return "other";
36546
- };
36547
- const preamble = buildChatPreamble({
36548
- projectId: chat2.project_id,
36549
- agents: allAgents
36550
- });
36551
- await new Promise((resolve2) => {
36552
- handleChatTurn({
36553
- chatId: chat2.id,
36554
- prompt: userMsg.text,
36555
- preferredRuntime: runtime4,
36556
- cwd,
36557
- systemPreamble: preamble,
36558
- log: this.opts.log,
36559
- onChunk: (text) => {
36560
- ensureTextOpen();
36561
- buffered += text;
36562
- agentReplyText += text;
36563
- flushText();
36564
- },
36565
- onToolCall: (ev) => {
36566
- closeText();
36567
- const partial2 = ev.status !== "completed" && ev.status !== "failed";
36568
- const status3 = ev.status || "in_progress";
36569
- if (ev.id) {
36570
- const existing = toolMsgByCallId.get(ev.id);
36571
- if (existing) {
36572
- peer.patchMessage(existing, {
36573
- partial: partial2,
36574
- status: status3,
36575
- tool: ev.tool,
36576
- tool_kind: mapToolKind(ev.kind),
36577
- input: summarizeInput(ev.input)
36578
- });
36579
- return;
36580
- }
36581
- }
36582
- const id3 = `tc_${ev.id || Math.random().toString(36).slice(2)}`;
36583
- const containerId = peer.appendStructured({
36584
- id: id3,
36585
- author: { kind: "agent", id: agentAuthorId, name: agentDisplayName },
36586
- kind: "tool_call",
36587
- text: "",
36588
- ts: Math.floor(Date.now() / 1000),
36589
- mentions: [],
36590
- attachments: [],
36591
- partial: partial2,
36592
- tool: ev.tool,
36593
- tool_kind: mapToolKind(ev.kind),
36594
- status: status3,
36595
- input: summarizeInput(ev.input),
36596
- tool_call_id: ev.id
36597
- });
36598
- if (ev.id)
36599
- toolMsgByCallId.set(ev.id, containerId);
36600
- },
36601
- onToolResult: (ev) => {
36602
- closeText();
36603
- const summary5 = summarizeContent(ev.content);
36604
- peer.appendStructured({
36605
- id: `tr_${ev.tool_call_id || Math.random().toString(36).slice(2)}_${Date.now()}`,
36606
- author: { kind: "agent", id: agentAuthorId, name: agentDisplayName },
36607
- kind: "tool_result",
36608
- text: `${summary5.lines} lines · ${summary5.bytes} bytes`,
36609
- ts: Math.floor(Date.now() / 1000),
36610
- mentions: [],
36611
- attachments: [],
36612
- partial: false,
36613
- tool_call_id: ev.tool_call_id,
36614
- content: summary5.content
36615
- });
36616
- const cid = ev.tool_call_id ? toolMsgByCallId.get(ev.tool_call_id) : undefined;
36617
- if (cid)
36618
- peer.patchMessage(cid, { partial: false, status: "completed" });
36619
- },
36620
- onDone: (stopReason) => {
36621
- closeText();
36622
- for (const cid of toolMsgByCallId.values()) {
36623
- peer.patchMessage(cid, { partial: false, status: "completed" });
36698
+ const trimmed = raw && raw.length > 240 ? raw.slice(0, 240) + "…" : raw || "";
36699
+ return { content: trimmed, bytes, lines };
36700
+ };
36701
+ const mapToolKind = (raw) => {
36702
+ const s = (raw || "").toLowerCase();
36703
+ if (s.includes("edit") || s.includes("modify"))
36704
+ return "edit";
36705
+ if (s.includes("read") || s.includes("view") || s.includes("fetch"))
36706
+ return "read";
36707
+ if (s.includes("write") || s.includes("create"))
36708
+ return "write";
36709
+ if (s.includes("execute") || s.includes("bash") || s.includes("shell") || s.includes("run"))
36710
+ return "bash";
36711
+ if (s.includes("search") || s.includes("grep") || s.includes("find"))
36712
+ return "search";
36713
+ return "other";
36714
+ };
36715
+ const preamble = buildChatPreamble({ projectId: chat2.project_id, agents: allAgents });
36716
+ await new Promise((resolve2) => {
36717
+ handleChatTurn({
36718
+ chatId: chat2.id,
36719
+ prompt: userMsg.text,
36720
+ preferredRuntime: runtime4,
36721
+ cwd,
36722
+ systemPreamble: preamble,
36723
+ log: log4,
36724
+ onChunk: (text) => {
36725
+ ensureTextOpen();
36726
+ buffered += text;
36727
+ agentReplyText += text;
36728
+ flushText();
36729
+ },
36730
+ onToolCall: (ev) => {
36731
+ closeText();
36732
+ const partial2 = ev.status !== "completed" && ev.status !== "failed";
36733
+ const status3 = ev.status || "in_progress";
36734
+ if (ev.id) {
36735
+ const existing = toolMsgByCallId.get(ev.id);
36736
+ if (existing) {
36737
+ peer.patchMessage(existing, {
36738
+ partial: partial2,
36739
+ status: status3,
36740
+ tool: ev.tool,
36741
+ tool_kind: mapToolKind(ev.kind),
36742
+ input: summarizeInput(ev.input)
36743
+ });
36744
+ return;
36624
36745
  }
36625
- this.opts.log(`[chat ${chat2.id}] done: ${stopReason}`);
36626
- resolve2();
36627
36746
  }
36628
- });
36629
- });
36630
- try {
36631
- const { actions, errors: errors3 } = parsePlanBlocks(agentReplyText);
36632
- if (actions.length || errors3.length) {
36633
- const res = await executeChatPlanActions(actions, errors3, {
36634
- apiUrl: this.opts.apiUrl,
36635
- wsId: this.opts.workspaceId,
36636
- chatId: chat2.id,
36637
- projectId: chat2.project_id,
36638
- agentId: resolvedAgentId,
36639
- log: this.opts.log
36747
+ const id3 = `tc_${ev.id || Math.random().toString(36).slice(2)}`;
36748
+ const containerId = peer.appendStructured({
36749
+ id: id3,
36750
+ author: { kind: "agent", id: agentAuthorId, name: agentDisplayName },
36751
+ kind: "tool_call",
36752
+ text: "",
36753
+ ts: Math.floor(Date.now() / 1000),
36754
+ mentions: [],
36755
+ attachments: [],
36756
+ partial: partial2,
36757
+ tool: ev.tool,
36758
+ tool_kind: mapToolKind(ev.kind),
36759
+ status: status3,
36760
+ input: summarizeInput(ev.input),
36761
+ tool_call_id: ev.id
36640
36762
  });
36641
- const summary5 = renderChatPlanSummary(res);
36642
- if (summary5) {
36643
- peer.appendStructured({
36644
- id: `sys_plan_${Date.now()}`,
36645
- author: { kind: "system", id: "multi-plan", name: "multi" },
36646
- kind: "text",
36647
- text: summary5,
36648
- ts: Math.floor(Date.now() / 1000),
36649
- mentions: [],
36650
- attachments: [],
36651
- partial: false
36652
- });
36763
+ if (ev.id)
36764
+ toolMsgByCallId.set(ev.id, containerId);
36765
+ },
36766
+ onToolResult: (ev) => {
36767
+ closeText();
36768
+ const summary5 = summarizeContent(ev.content);
36769
+ peer.appendStructured({
36770
+ id: `tr_${ev.tool_call_id || Math.random().toString(36).slice(2)}_${Date.now()}`,
36771
+ author: { kind: "agent", id: agentAuthorId, name: agentDisplayName },
36772
+ kind: "tool_result",
36773
+ text: `${summary5.lines} lines · ${summary5.bytes} bytes`,
36774
+ ts: Math.floor(Date.now() / 1000),
36775
+ mentions: [],
36776
+ attachments: [],
36777
+ partial: false,
36778
+ tool_call_id: ev.tool_call_id,
36779
+ content: summary5.content
36780
+ });
36781
+ const cid = ev.tool_call_id ? toolMsgByCallId.get(ev.tool_call_id) : undefined;
36782
+ if (cid)
36783
+ peer.patchMessage(cid, { partial: false, status: "completed" });
36784
+ },
36785
+ onDone: (stopReason) => {
36786
+ closeText();
36787
+ for (const cid of toolMsgByCallId.values()) {
36788
+ peer.patchMessage(cid, { partial: false, status: "completed" });
36653
36789
  }
36654
- this.opts.log(`[chat ${chat2.id}] plan exec: ${res.ok} ok, ${res.fail} fail, ${errors3.length} parse-err`);
36790
+ log4(`[chat ${chat2.id}] done: ${stopReason}`);
36791
+ resolve2();
36655
36792
  }
36656
- const ui = parseUiBlocks(agentReplyText);
36657
- if (ui.blocks.length)
36658
- this.opts.log(`[chat ${chat2.id}] ui blocks: ${ui.blocks.length}`);
36659
- if (ui.errors.length)
36660
- this.opts.log(`[chat ${chat2.id}] ui parse err: ${ui.errors.length}`);
36661
- } catch (e) {
36662
- this.opts.log(`[chat ${chat2.id}] post-turn err: ${e.message}`);
36663
- }
36664
- if (/^new chat$/i.test(chat2.title) && agentReplyText.trim()) {
36665
- this.autoTitle(chat2, userMsg.text, agentReplyText).catch((e) => this.opts.log(`[chat ${chat2.id}] auto-title failed: ${e.message}`));
36793
+ });
36794
+ });
36795
+ try {
36796
+ const { actions, errors: errors3 } = parsePlanBlocks(agentReplyText);
36797
+ if (actions.length || errors3.length) {
36798
+ const res = await executeChatPlanActions(actions, errors3, {
36799
+ apiUrl,
36800
+ wsId: workspaceId,
36801
+ chatId: chat2.id,
36802
+ projectId: chat2.project_id,
36803
+ agentId: resolvedAgentId,
36804
+ log: log4
36805
+ });
36806
+ const summary5 = renderChatPlanSummary(res);
36807
+ if (summary5) {
36808
+ peer.appendStructured({
36809
+ id: `sys_plan_${Date.now()}`,
36810
+ author: { kind: "system", id: "multi-plan", name: "multi" },
36811
+ kind: "text",
36812
+ text: summary5,
36813
+ ts: Math.floor(Date.now() / 1000),
36814
+ mentions: [],
36815
+ attachments: [],
36816
+ partial: false
36817
+ });
36818
+ }
36819
+ log4(`[chat ${chat2.id}] plan exec: ${res.ok} ok, ${res.fail} fail, ${errors3.length} parse-err`);
36666
36820
  }
36821
+ const ui = parseUiBlocks(agentReplyText);
36822
+ if (ui.blocks.length)
36823
+ log4(`[chat ${chat2.id}] ui blocks: ${ui.blocks.length}`);
36824
+ if (ui.errors.length)
36825
+ log4(`[chat ${chat2.id}] ui parse err: ${ui.errors.length}`);
36826
+ } catch (e) {
36827
+ log4(`[chat ${chat2.id}] post-turn err: ${e.message}`);
36667
36828
  }
36668
- async autoTitle(chat2, userMsg, agentReply) {
36669
- const runtime4 = await this.resolveAgentRuntime(chat2, chat2.primary_agent_id);
36670
- const trimUser = userMsg.slice(0, 800);
36671
- const trimAgent = agentReply.slice(0, 1200);
36672
- const prompt = `Reply with ONLY a 3-6 word title for this conversation. No quotes, no punctuation, no explanation. Capitalize each word.
36829
+ if (/^new chat$/i.test(chat2.title) && agentReplyText.trim()) {
36830
+ autoTitle(chat2, userMsg.text, agentReplyText, ctx).catch((e) => log4(`[chat ${chat2.id}] auto-title failed: ${e.message}`));
36831
+ }
36832
+ }
36833
+ async function autoTitle(chat2, userMsg, agentReply, ctx) {
36834
+ const { apiUrl, authToken: authToken2, workspaceId, log: log4 } = ctx;
36835
+ const runtime4 = await resolveAgentRuntime(apiUrl, workspaceId, authToken2, chat2, chat2.primary_agent_id);
36836
+ const trimUser = userMsg.slice(0, 800);
36837
+ const trimAgent = agentReply.slice(0, 1200);
36838
+ const prompt = `Reply with ONLY a 3-6 word title for this conversation. No quotes, no punctuation, no explanation. Capitalize each word.
36673
36839
 
36674
36840
  ` + `User: ${trimUser}
36675
36841
 
36676
36842
  Assistant: ${trimAgent}`;
36677
- let collected = "";
36678
- await new Promise((resolve2) => {
36679
- handleChatTurn({
36680
- chatId: `${chat2.id}__title`,
36681
- prompt,
36682
- preferredRuntime: runtime4,
36683
- log: this.opts.log,
36684
- onChunk: (t) => {
36685
- collected += t;
36686
- },
36687
- onDone: () => resolve2()
36688
- });
36843
+ let collected = "";
36844
+ await new Promise((resolve2) => {
36845
+ handleChatTurn({
36846
+ chatId: `${chat2.id}__title`,
36847
+ prompt,
36848
+ preferredRuntime: runtime4,
36849
+ log: log4,
36850
+ onChunk: (t) => {
36851
+ collected += t;
36852
+ },
36853
+ onDone: () => resolve2()
36689
36854
  });
36690
- const cleaned = collected.split(`
36855
+ });
36856
+ const cleaned = collected.split(`
36691
36857
  `).map((s) => s.trim()).filter(Boolean)[0] ?? "";
36692
- const stripped = cleaned.replace(/^["'`*_#-]+|["'`*_#-]+$/g, "").trim();
36693
- const final = stripped.split(/\s+/).slice(0, 8).join(" ").slice(0, 60);
36694
- if (!final || /^new chat$/i.test(final))
36695
- return;
36696
- try {
36697
- const r = await fetch(`${this.opts.apiUrl}/api/workspaces/${this.opts.workspaceId}/chats/${chat2.id}`, {
36698
- method: "PATCH",
36699
- headers: {
36700
- authorization: `Bearer ${this.opts.authToken}`,
36701
- "content-type": "application/json"
36702
- },
36703
- body: JSON.stringify({ title: final })
36704
- });
36705
- if (r.ok)
36706
- this.opts.log(`[chat ${chat2.id}] auto-titled: ${final}`);
36707
- } catch {}
36708
- }
36858
+ const stripped = cleaned.replace(/^["'`*_#-]+|["'`*_#-]+$/g, "").trim();
36859
+ const final = stripped.split(/\s+/).slice(0, 8).join(" ").slice(0, 60);
36860
+ if (!final || /^new chat$/i.test(final))
36861
+ return;
36862
+ try {
36863
+ const r = await fetch(`${apiUrl}/api/workspaces/${workspaceId}/chats/${chat2.id}`, {
36864
+ method: "PATCH",
36865
+ headers: { authorization: `Bearer ${authToken2}`, "content-type": "application/json" },
36866
+ body: JSON.stringify({ title: final })
36867
+ });
36868
+ if (r.ok)
36869
+ log4(`[chat ${chat2.id}] auto-titled: ${final}`);
36870
+ } catch {}
36709
36871
  }
36710
36872
  function buildChatPreamble(args2) {
36711
36873
  const projectLine = args2.projectId ? `Default project: ${args2.projectId} (issue.create / issue.list use it when project_id is omitted).` : `No default project pinned to this chat. issue.create requires explicit project_id; otherwise the host returns [err] and skips.`;
@@ -36733,7 +36895,7 @@ Action vocabulary:
36733
36895
  {"type":"issue.delete_where","status":"todo","limit":50},
36734
36896
  {"type":"issue.list","status":"todo","limit":20},
36735
36897
  {"type":"issue.search","query":"flaky tests","limit":10},
36736
- {"type":"agent.create","name":"refactor-bot","agent_type":"claude-code","prompt":"...","allowed_tools":["Read","Edit","Bash"]},
36898
+ {"type":"agent.create","name":"refactor-bot","prompt":"...","allowed_tools":["Read","Edit","Bash"]},
36737
36899
  {"type":"agent.update","id":"ag_xxx","prompt":"..."},
36738
36900
  {"type":"skill.create","name":"run-tests","description":"...","body":"---\\nname: run-tests\\n---\\n..."},
36739
36901
  {"type":"skill.attach","agent_id":"ag_xxx","skill_id":"sk_yyy"},
@@ -37334,7 +37496,7 @@ import { parseArgs } from "util";
37334
37496
  // package.json
37335
37497
  var package_default = {
37336
37498
  name: "@shipers-dev/multi",
37337
- version: "0.39.2",
37499
+ version: "0.43.0",
37338
37500
  type: "module",
37339
37501
  bin: {
37340
37502
  "multi-agent": "./dist/index.js"
@@ -37619,8 +37781,8 @@ init_errors();
37619
37781
 
37620
37782
  class Worktree extends exports_Effect.Service()("cli/Worktree", {
37621
37783
  succeed: {
37622
- ensure: (workingDir, issueKey, issueId) => exports_Effect.tryPromise({
37623
- try: () => ensureWorktree(workingDir, issueKey, issueId),
37784
+ ensure: (workingDir, issueKey, issueId, opts) => exports_Effect.tryPromise({
37785
+ try: () => ensureWorktree(workingDir, issueKey, issueId, opts),
37624
37786
  catch: (cause3) => new WorktreeError({ message: `ensureWorktree failed`, cause: cause3 })
37625
37787
  }),
37626
37788
  isGitRepo: (dir) => exports_Effect.tryPromise({
@@ -38346,8 +38508,8 @@ init_errors();
38346
38508
 
38347
38509
  class Worktree2 extends exports_Effect.Service()("cli/Worktree", {
38348
38510
  succeed: {
38349
- ensure: (workingDir, issueKey, issueId) => exports_Effect.tryPromise({
38350
- try: () => ensureWorktree(workingDir, issueKey, issueId),
38511
+ ensure: (workingDir, issueKey, issueId, opts) => exports_Effect.tryPromise({
38512
+ try: () => ensureWorktree(workingDir, issueKey, issueId, opts),
38351
38513
  catch: (cause3) => new WorktreeError({ message: `ensureWorktree failed`, cause: cause3 })
38352
38514
  }),
38353
38515
  isGitRepo: (dir) => exports_Effect.tryPromise({
@@ -38765,6 +38927,34 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
38765
38927
  }
38766
38928
  })();
38767
38929
  }
38930
+ if (url2.pathname === "/run-chat-turn" && req.method === "POST") {
38931
+ if (req.headers.get("authorization") !== expectedAuth)
38932
+ return new Response("unauthorized", { status: 401 });
38933
+ return (async () => {
38934
+ try {
38935
+ const body = await req.json();
38936
+ if (!body?.chat_id || !body?.message_id) {
38937
+ return Response.json({ error: "chat_id and message_id required" }, { status: 400 });
38938
+ }
38939
+ if (!cfg.workspaceId || !cfg.authToken || !cfg.deviceId) {
38940
+ return Response.json({ error: "daemon not configured" }, { status: 503 });
38941
+ }
38942
+ const { runChatTurn: runChatTurn2 } = await Promise.resolve().then(() => (init_chat_supervisor(), exports_chat_supervisor));
38943
+ runChatTurn2({
38944
+ apiUrl,
38945
+ authToken: cfg.authToken,
38946
+ workspaceId: cfg.workspaceId,
38947
+ deviceId: cfg.deviceId,
38948
+ chatId: body.chat_id,
38949
+ messageId: body.message_id,
38950
+ log: log3
38951
+ }).catch((e) => log3(`[chat ${body.chat_id}] runChatTurn error: ${e.message}`));
38952
+ return Response.json({ accepted: true }, { status: 202 });
38953
+ } catch (e) {
38954
+ return Response.json({ error: String(e) }, { status: 400 });
38955
+ }
38956
+ })();
38957
+ }
38768
38958
  if (url2.pathname === "/stop" && req.method === "POST") {
38769
38959
  if (req.headers.get("authorization") !== expectedAuth)
38770
38960
  return new Response("unauthorized", { status: 401 });
@@ -38826,19 +39016,6 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
38826
39016
  catch: () => new DaemonError({ message: "materializeBundle skipped" })
38827
39017
  }).pipe(exports_Effect.catchAll(() => exports_Effect.void));
38828
39018
  const schedFiber = yield* exports_Effect.forkIn(daemonScope)(schedulerLoop(state, daemonScope));
38829
- let chatSup = null;
38830
- if (cfg.workspaceId && cfg.authToken && cfg.deviceId) {
38831
- const { ChatSupervisor: ChatSupervisor2 } = yield* exports_Effect.promise(() => Promise.resolve().then(() => (init_chat_supervisor(), exports_chat_supervisor)));
38832
- chatSup = new ChatSupervisor2({
38833
- apiUrl,
38834
- authToken: cfg.authToken,
38835
- workspaceId: cfg.workspaceId,
38836
- deviceId: cfg.deviceId,
38837
- log: log3
38838
- });
38839
- chatSup.start();
38840
- log3(`Chat supervisor started (device=${cfg.deviceId})`);
38841
- }
38842
39019
  if (cfg.workspaceId) {
38843
39020
  yield* exports_Effect.forkIn(daemonScope)(exports_Effect.tryPromise({
38844
39021
  try: () => drainOfflineDispatches(apiUrl, cfg.deviceId, cfg.workspaceId, cfg.dispatchSecret, db2, () => {
@@ -38928,9 +39105,6 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
38928
39105
  try {
38929
39106
  server.stop();
38930
39107
  } catch {}
38931
- try {
38932
- chatSup?.close();
38933
- } catch {}
38934
39108
  try {
38935
39109
  tunnel?.child?.kill();
38936
39110
  } catch {}