@shipers-dev/multi 0.39.2 → 0.41.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
@@ -33802,7 +33802,7 @@ function parsePlanBlocks(text) {
33802
33802
  }
33803
33803
  return { actions, errors: errors3 };
33804
33804
  }
33805
- var PLAN_SCHEMA_VERSION = 2, Priority, AssigneeType, IssueStatus, SessionRole, SkillFile, PlanActionSchema, PlanEnvelopeSchema, UiBlockSchema, UI_FENCE_RE, FENCE_RE;
33805
+ var PLAN_SCHEMA_VERSION = 5, Priority, AssigneeType, IssueStatus, SessionRole, SkillFile, EvalPolicy, PlanActionSchema, PlanEnvelopeSchema, UiBlockSchema, UI_FENCE_RE, FENCE_RE;
33806
33806
  var init_plans = __esm(() => {
33807
33807
  init_zod();
33808
33808
  Priority = exports_external.enum(["low", "medium", "high"]);
@@ -33810,6 +33810,12 @@ var init_plans = __esm(() => {
33810
33810
  IssueStatus = exports_external.enum(["todo", "in_progress", "done", "failed", "stopped", "cancelled"]);
33811
33811
  SessionRole = exports_external.enum(["implementer", "reviewer", "test-fixer"]);
33812
33812
  SkillFile = exports_external.object({ path: exports_external.string().min(1), content: exports_external.string() });
33813
+ EvalPolicy = exports_external.object({
33814
+ evaluator_agent_id: exports_external.string().min(1),
33815
+ threshold: exports_external.number().min(0).max(1),
33816
+ on_fail: exports_external.enum(["retry", "fail", "comment"]).optional(),
33817
+ max_retries: exports_external.number().int().min(0).max(10).optional()
33818
+ });
33813
33819
  PlanActionSchema = exports_external.discriminatedUnion("type", [
33814
33820
  exports_external.object({
33815
33821
  type: exports_external.literal("create"),
@@ -33820,7 +33826,9 @@ var init_plans = __esm(() => {
33820
33826
  assignee_type: AssigneeType.optional(),
33821
33827
  assignee_id: exports_external.string().optional(),
33822
33828
  parent_id: exports_external.string().optional(),
33823
- blocked_by: exports_external.array(exports_external.string().min(1)).optional()
33829
+ blocked_by: exports_external.array(exports_external.string().min(1)).optional(),
33830
+ await_children: exports_external.boolean().optional(),
33831
+ eval_policy: EvalPolicy.optional()
33824
33832
  }),
33825
33833
  exports_external.object({
33826
33834
  type: exports_external.literal("update"),
@@ -33831,7 +33839,8 @@ var init_plans = __esm(() => {
33831
33839
  priority: Priority.optional(),
33832
33840
  assignee_type: AssigneeType.optional(),
33833
33841
  assignee_id: exports_external.string().optional(),
33834
- blocked_by: exports_external.array(exports_external.string().min(1)).optional()
33842
+ blocked_by: exports_external.array(exports_external.string().min(1)).optional(),
33843
+ eval_policy: EvalPolicy.nullable().optional()
33835
33844
  }),
33836
33845
  exports_external.object({
33837
33846
  type: exports_external.literal("delegate"),
@@ -33870,7 +33879,7 @@ var init_plans = __esm(() => {
33870
33879
  exports_external.object({
33871
33880
  type: exports_external.literal("agent.create"),
33872
33881
  name: exports_external.string().min(1).max(120),
33873
- agent_type: exports_external.string().min(1),
33882
+ agent_type: exports_external.string().min(1).optional(),
33874
33883
  prompt: exports_external.string().optional(),
33875
33884
  skill_ids: exports_external.array(exports_external.string()).optional(),
33876
33885
  allowed_tools: exports_external.array(exports_external.string()).optional()
@@ -33906,6 +33915,13 @@ var init_plans = __esm(() => {
33906
33915
  agent_id: exports_external.string().min(1),
33907
33916
  role: SessionRole,
33908
33917
  device_id: exports_external.string().optional()
33918
+ }),
33919
+ exports_external.object({
33920
+ type: exports_external.literal("eval.submit"),
33921
+ id: exports_external.string().min(1).optional(),
33922
+ score: exports_external.number().min(0).max(1),
33923
+ feedback: exports_external.string().min(1).max(8000),
33924
+ scores: exports_external.record(exports_external.string(), exports_external.number().min(0).max(1)).optional()
33909
33925
  })
33910
33926
  ]);
33911
33927
  PlanEnvelopeSchema = exports_external.object({
@@ -34925,7 +34941,7 @@ Agent + skill self-service (use sparingly — only when you genuinely need a new
34925
34941
 
34926
34942
  \`\`\`multi-plan
34927
34943
  {"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"]},
34944
+ {"type":"agent.create","name":"refactor-bot","prompt":"You refactor TS code...","skill_ids":["sk_xxx"],"allowed_tools":["Read","Edit","Bash"]},
34929
34945
  {"type":"agent.update","id":"ag_xxx","prompt":"new prompt..."},
34930
34946
  {"type":"skill.create","name":"run-tests","description":"Run the test suite","body":"---\\nname: run-tests\\n---\\n\\n# Run tests\\n..."},
34931
34947
  {"type":"skill.attach","agent_id":"ag_xxx","skill_id":"sk_yyy"},
@@ -34950,8 +34966,11 @@ ${agentsBlock}
34950
34966
  }
34951
34967
  async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors = []) {
34952
34968
  const lines = [];
34953
- for (const e of parseErrors)
34969
+ const results = [];
34970
+ for (const e of parseErrors) {
34954
34971
  lines.push(`- [err] plan parse: ${e.message}`);
34972
+ results.push({ type: "parse", status: "error", error: e.message });
34973
+ }
34955
34974
  let truncated = false;
34956
34975
  if (actions.length > PLAN_ACTION_LIMIT) {
34957
34976
  truncated = true;
@@ -34961,8 +34980,10 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
34961
34980
  if (depth >= PLANNING_DEPTH_LIMIT) {
34962
34981
  const blocked3 = actions.filter((a) => a.type !== "update").length;
34963
34982
  actions = actions.filter((a) => a.type === "update");
34964
- if (blocked3)
34983
+ if (blocked3) {
34965
34984
  lines.push(`- [warn] ${blocked3} non-update action(s) blocked (planning depth limit ${PLANNING_DEPTH_LIMIT})`);
34985
+ results.push({ type: "note", status: "note", message: `${blocked3} non-update action(s) blocked (planning depth limit ${PLANNING_DEPTH_LIMIT})` });
34986
+ }
34966
34987
  }
34967
34988
  const SUBCAPS = { "agent.create": 2, "skill.create": 3, "skill.attach": 5, "skill.detach": 5, "agent.update": 5, "session.create": 3 };
34968
34989
  const counts = {};
@@ -34973,6 +34994,7 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
34973
34994
  counts[a.type] = (counts[a.type] || 0) + 1;
34974
34995
  if (counts[a.type] > cap) {
34975
34996
  lines.push(`- [warn] ${a.type} sub-cap ${cap} hit, dropping extra`);
34997
+ results.push({ type: a.type, status: "note", message: `${a.type} sub-cap ${cap} hit, dropping extra` });
34976
34998
  return false;
34977
34999
  }
34978
35000
  return true;
@@ -35013,17 +35035,21 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
35013
35035
  const res = await apiClient.post(mutateUrl, body, { headers });
35014
35036
  if (!res.success) {
35015
35037
  lines.push(`- [err] create "${a.title}": ${res.error || res.status}`);
35038
+ results.push({ type: "create", status: "error", error: String(res.error || res.status), label: a.title });
35016
35039
  continue;
35017
35040
  }
35018
35041
  const created = res.data;
35019
35042
  lines.push(`- [ok] created **${created.key}** - ${created.title}${created.assignee_id ? ` -> @${created.assignee_id}` : ""} (autonomy=${created.autonomy_level || "auto"})`);
35043
+ 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
35044
  } else if (a.type === "update") {
35021
35045
  const res = await apiClient.post(mutateUrl, { action: "update", ...a }, { headers });
35022
35046
  if (!res.success) {
35023
35047
  lines.push(`- [err] update ${a.id}: ${res.error || res.status}`);
35048
+ results.push({ type: "update", status: "error", error: String(res.error || res.status), label: a.id });
35024
35049
  continue;
35025
35050
  }
35026
35051
  lines.push(`- [ok] updated ${res.data.key}`);
35052
+ 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
35053
  if ((a.status === "done" || a.status === "cancelled") && parentTask.working_dir && existsSync11(parentTask.working_dir)) {
35028
35054
  const targetKey = res.data?.key;
35029
35055
  if (targetKey) {
@@ -35038,9 +35064,11 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
35038
35064
  const res = await apiClient.post(mutateUrl, { action: "update", id: a.id, assignee_type: "agent", assignee_id: a.assignee_id, status: "todo" }, { headers });
35039
35065
  if (!res.success) {
35040
35066
  lines.push(`- [err] delegate ${a.id}: ${res.error || res.status}`);
35067
+ results.push({ type: "delegate", status: "error", error: String(res.error || res.status), label: a.id });
35041
35068
  continue;
35042
35069
  }
35043
35070
  lines.push(`- [ok] delegated ${res.data.key} -> ${a.assignee_id}`);
35071
+ results.push({ type: "delegate", status: "ok", issue_id: res.data.id, key: res.data.key, assignee_id: a.assignee_id });
35044
35072
  } else if (a.type === "handoff") {
35045
35073
  const res = await apiClient.post(mutateUrl, {
35046
35074
  action: "handoff",
@@ -35052,17 +35080,21 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
35052
35080
  }, { headers });
35053
35081
  if (!res.success) {
35054
35082
  lines.push(`- [err] handoff -> ${a.target_agent_id}: ${res.error || res.status}`);
35083
+ results.push({ type: "handoff", status: "error", error: String(res.error || res.status), label: a.target_agent_id });
35055
35084
  continue;
35056
35085
  }
35057
35086
  const blocked3 = res.data?.blocked ? " (blocked parent)" : "";
35058
35087
  lines.push(`- [ok] handoff -> ${a.target_agent_id} via **${res.data?.child_key}**${blocked3} expect=${a.expect ?? "summary"}`);
35088
+ 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
35089
  } else if (a.type === "issue.delete") {
35060
35090
  const res = await apiClient.post(mutateUrl, { action: "delete", id: a.id }, { headers });
35061
35091
  if (!res.success) {
35062
35092
  lines.push(`- [err] issue.delete ${a.id}: ${res.error || res.status}`);
35093
+ results.push({ type: "issue.delete", status: "error", error: String(res.error || res.status), label: a.id });
35063
35094
  continue;
35064
35095
  }
35065
35096
  lines.push(`- [ok] deleted ${res.data?.key || a.id}`);
35097
+ results.push({ type: "issue.delete", status: "ok", key: res.data?.key || a.id, issue_id: res.data?.id || a.id });
35066
35098
  } else if (a.type === "issue.delete_where") {
35067
35099
  const res = await apiClient.post(mutateUrl, {
35068
35100
  action: "delete_where",
@@ -35073,17 +35105,20 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
35073
35105
  }, { headers });
35074
35106
  if (!res.success) {
35075
35107
  lines.push(`- [err] issue.delete_where: ${res.error || res.status}`);
35108
+ results.push({ type: "issue.delete_where", status: "error", error: String(res.error || res.status) });
35076
35109
  continue;
35077
35110
  }
35078
35111
  const rows = Array.isArray(res.data?.deleted) ? res.data.deleted : [];
35079
35112
  const count3 = res.data?.count ?? rows.length;
35080
35113
  if (!count3) {
35081
35114
  lines.push(`- [ok] issue.delete_where: 0 matches`);
35115
+ results.push({ type: "issue.delete_where", status: "ok", count: 0, keys: [] });
35082
35116
  continue;
35083
35117
  }
35084
35118
  lines.push(`- [ok] issue.delete_where: deleted ${count3} issue(s)`);
35085
35119
  for (const r of rows)
35086
35120
  lines.push(` - ${r.key}`);
35121
+ results.push({ type: "issue.delete_where", status: "ok", count: count3, keys: rows.map((r) => r.key) });
35087
35122
  } else if (a.type === "issue.list") {
35088
35123
  const res = await apiClient.post(queryUrl, {
35089
35124
  kind: "list",
@@ -35094,17 +35129,20 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
35094
35129
  }, { headers });
35095
35130
  if (!res.success) {
35096
35131
  lines.push(`- [err] issue.list: ${res.error || res.status}`);
35132
+ results.push({ type: "issue.list", status: "error", error: String(res.error || res.status) });
35097
35133
  continue;
35098
35134
  }
35099
35135
  const rows = Array.isArray(res.data) ? res.data : Array.isArray(res.data?.results) ? res.data.results : [];
35100
35136
  if (!rows.length) {
35101
35137
  lines.push(`- [ok] issue.list: 0 issues`);
35138
+ results.push({ type: "issue.list", status: "ok", count: 0 });
35102
35139
  continue;
35103
35140
  }
35104
35141
  lines.push(`- [ok] issue.list: ${rows.length} issue(s)`);
35105
35142
  for (const r of rows) {
35106
35143
  lines.push(` - **${r.key}** [${r.status}] ${r.title}${r.assignee_id ? ` (@${r.assignee_id})` : ""}`);
35107
35144
  }
35145
+ results.push({ type: "issue.list", status: "ok", count: rows.length });
35108
35146
  } else if (a.type === "issue.search") {
35109
35147
  const res = await apiClient.post(queryUrl, {
35110
35148
  kind: "search",
@@ -35114,75 +35152,95 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
35114
35152
  }, { headers });
35115
35153
  if (!res.success) {
35116
35154
  lines.push(`- [err] issue.search "${a.query}": ${res.error || res.status}`);
35155
+ results.push({ type: "issue.search", status: "error", error: String(res.error || res.status), label: a.query });
35117
35156
  continue;
35118
35157
  }
35119
35158
  const rows = Array.isArray(res.data) ? res.data : Array.isArray(res.data?.results) ? res.data.results : [];
35120
35159
  if (!rows.length) {
35121
35160
  lines.push(`- [ok] issue.search "${a.query}": 0 hits`);
35161
+ results.push({ type: "issue.search", status: "ok", count: 0, query: a.query });
35122
35162
  continue;
35123
35163
  }
35124
35164
  lines.push(`- [ok] issue.search "${a.query}": ${rows.length} hit(s)`);
35125
35165
  for (const r of rows) {
35126
35166
  lines.push(` - **${r.key}** [${r.status}] ${r.title}`);
35127
35167
  }
35168
+ results.push({ type: "issue.search", status: "ok", count: rows.length, query: a.query });
35128
35169
  } else if (a.type === "agent.create") {
35129
35170
  if (!parentWsId) {
35130
35171
  lines.push(`- [err] agent.create "${a.name}": no tenant workspace id`);
35172
+ results.push({ type: "agent.create", status: "error", error: "no tenant workspace id", label: a.name });
35131
35173
  continue;
35132
35174
  }
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 });
35175
+ 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
35176
  if (!res.success) {
35135
35177
  lines.push(`- [err] agent.create "${a.name}": ${res.error || res.status}`);
35178
+ results.push({ type: "agent.create", status: "error", error: String(res.error || res.status), label: a.name });
35136
35179
  continue;
35137
35180
  }
35138
35181
  lines.push(`- [ok] agent.create "${a.name}" -> ${res.data?.agent_id}`);
35182
+ 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
35183
  } else if (a.type === "agent.update") {
35140
35184
  if (!parentWsId) {
35141
35185
  lines.push(`- [err] agent.update ${a.id}: no tenant workspace id`);
35186
+ results.push({ type: "agent.update", status: "error", error: "no tenant workspace id", label: a.id });
35142
35187
  continue;
35143
35188
  }
35144
35189
  const res = await apiClient.post(`${apiUrl}/api/workspaces/${parentWsId}/agent_ops/agents/mutate`, { action: "update", ...a }, { headers });
35145
35190
  if (!res.success) {
35146
35191
  lines.push(`- [err] agent.update ${a.id}: ${res.error || res.status}`);
35192
+ results.push({ type: "agent.update", status: "error", error: String(res.error || res.status), label: a.id });
35147
35193
  continue;
35148
35194
  }
35149
- if (res.data?.queued)
35195
+ const changed = ["name", "prompt", "allowed_tools"].filter((k) => a[k] !== undefined);
35196
+ if (res.data?.queued) {
35150
35197
  lines.push(`- [pending] agent.update ${a.id} queued`);
35151
- else
35198
+ results.push({ type: "agent.update", status: "ok", agent_id: a.id, queued: true, pending_op_id: res.data?.pending_op_id, changed });
35199
+ } else {
35152
35200
  lines.push(`- [ok] agent.update ${a.id}`);
35201
+ results.push({ type: "agent.update", status: "ok", agent_id: a.id, queued: false, changed });
35202
+ }
35153
35203
  } else if (a.type === "skill.create") {
35154
35204
  if (!parentWsId) {
35155
35205
  lines.push(`- [err] skill.create "${a.name}": no tenant workspace id`);
35206
+ results.push({ type: "skill.create", status: "error", error: "no tenant workspace id", label: a.name });
35156
35207
  continue;
35157
35208
  }
35158
35209
  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
35210
  if (!res.success) {
35160
35211
  lines.push(`- [err] skill.create "${a.name}": ${res.error || res.status}`);
35212
+ results.push({ type: "skill.create", status: "error", error: String(res.error || res.status), label: a.name });
35161
35213
  continue;
35162
35214
  }
35163
35215
  lines.push(`- [pending] skill.create "${a.name}" queued for human review (op ${res.data?.pending_op_id})`);
35216
+ results.push({ type: "skill.create", status: "ok", pending_op_id: res.data?.pending_op_id, name: a.name, description: a.description });
35164
35217
  } else if (a.type === "session.create") {
35165
35218
  if (!parentWsId) {
35166
35219
  lines.push(`- [err] session.create role=${a.role}: no tenant workspace id`);
35220
+ results.push({ type: "session.create", status: "error", error: "no tenant workspace id" });
35167
35221
  continue;
35168
35222
  }
35169
35223
  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
35224
  if (!res.success) {
35171
35225
  lines.push(`- [err] session.create role=${a.role}: ${res.error || res.status}`);
35226
+ results.push({ type: "session.create", status: "error", error: String(res.error || res.status) });
35172
35227
  continue;
35173
35228
  }
35174
35229
  const sess = res.data?.session;
35175
35230
  const reused = res.data?.reused;
35176
35231
  lines.push(`- [${reused ? "reuse" : "ok"}] session.${reused ? "reuse" : "create"} ${sess?.id?.slice(0, 8) || "?"} (role=${a.role})`);
35232
+ results.push({ type: "session.create", status: "ok", session_id: sess?.id, role: a.role, agent_id: a.agent_id, reused: !!reused });
35177
35233
  } else if (a.type === "eval.submit") {
35178
35234
  if (!parentWsId) {
35179
35235
  lines.push(`- [err] eval.submit: no tenant workspace id`);
35236
+ results.push({ type: "eval.submit", status: "error", error: "no tenant workspace id" });
35180
35237
  continue;
35181
35238
  }
35182
35239
  const url2 = `${apiUrl}/api/workspaces/${parentWsId}/agent/issues/eval/submit`;
35183
35240
  const res = await apiClient.post(url2, { id: a.id, score: a.score, feedback: a.feedback, scores: a.scores }, { headers });
35184
35241
  if (!res.success) {
35185
35242
  lines.push(`- [err] eval.submit: ${res.error || res.status}`);
35243
+ results.push({ type: "eval.submit", status: "error", error: String(res.error || res.status) });
35186
35244
  continue;
35187
35245
  }
35188
35246
  const verdict = res.data?.verdict;
@@ -35190,34 +35248,48 @@ async function executePlanActions(apiUrl, parentTask, actions, ctx, parseErrors
35190
35248
  const attempt = res.data?.attempt;
35191
35249
  const tag3 = verdict === "pass" ? "ok" : "warn";
35192
35250
  lines.push(`- [${tag3}] eval.submit verdict=${verdict} score=${a.score.toFixed(2)} attempt=${attempt}${action ? ` action=${action}` : ""}`);
35251
+ results.push({ type: "eval.submit", status: "ok", verdict, score: a.score, attempt, action_taken: action });
35193
35252
  } else if (a.type === "skill.attach" || a.type === "skill.detach") {
35194
35253
  const action = a.type === "skill.attach" ? "attach_skill" : "detach_skill";
35195
35254
  if (!parentWsId) {
35196
35255
  lines.push(`- [err] ${a.type}: no tenant workspace id`);
35256
+ results.push({ type: a.type, status: "error", error: "no tenant workspace id" });
35197
35257
  continue;
35198
35258
  }
35199
35259
  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
35260
  if (!res.success) {
35201
35261
  lines.push(`- [err] ${a.type} ${a.skill_id}->${a.agent_id}: ${res.error || res.status}`);
35262
+ results.push({ type: a.type, status: "error", error: String(res.error || res.status), label: `${a.skill_id}->${a.agent_id}` });
35202
35263
  continue;
35203
35264
  }
35204
- if (res.data?.queued)
35265
+ if (res.data?.queued) {
35205
35266
  lines.push(`- [pending] ${a.type} queued`);
35206
- else
35267
+ 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 });
35268
+ } else {
35207
35269
  lines.push(`- [ok] ${a.type} ${a.skill_id} <-> ${a.agent_id}`);
35270
+ results.push({ type: a.type, status: "ok", agent_id: a.agent_id, skill_id: a.skill_id, queued: false });
35271
+ }
35208
35272
  }
35209
35273
  } catch (e) {
35210
35274
  lines.push(`- [err] ${a.type} failed: ${String(e)}`);
35275
+ results.push({ type: a.type, status: "error", error: String(e) });
35211
35276
  }
35212
35277
  }
35213
- if (truncated)
35278
+ if (truncated) {
35214
35279
  lines.push(`- [warn] action list truncated at ${PLAN_ACTION_LIMIT}`);
35280
+ results.push({ type: "note", status: "note", message: `Action list truncated at ${PLAN_ACTION_LIMIT}` });
35281
+ }
35215
35282
  if (!lines.length)
35216
35283
  return "";
35284
+ const json2 = JSON.stringify({ version: 1, actions: results });
35217
35285
  return `**Planning actions**
35218
35286
 
35219
35287
  ${lines.join(`
35220
- `)}`;
35288
+ `)}
35289
+
35290
+ \`\`\`multi-actions
35291
+ ${json2}
35292
+ \`\`\``;
35221
35293
  }
35222
35294
  function stripMd(s) {
35223
35295
  return s.replace(/[`*_]/g, "").trim();
@@ -35822,6 +35894,22 @@ class ChatPeer {
35822
35894
  getDoc() {
35823
35895
  return this.doc;
35824
35896
  }
35897
+ findMessage(id3) {
35898
+ for (const m of listMessages(this.doc))
35899
+ if (m.id === id3)
35900
+ return m;
35901
+ return null;
35902
+ }
35903
+ async awaitMessage(id3, timeoutMs = 5000) {
35904
+ const start3 = Date.now();
35905
+ while (Date.now() - start3 < timeoutMs) {
35906
+ const found = this.findMessage(id3);
35907
+ if (found)
35908
+ return found;
35909
+ await new Promise((r) => setTimeout(r, 50));
35910
+ }
35911
+ return null;
35912
+ }
35825
35913
  start() {
35826
35914
  this.connect();
35827
35915
  }
@@ -35898,8 +35986,9 @@ class ChatPeer {
35898
35986
  if (before2.has(m.id))
35899
35987
  continue;
35900
35988
  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}`));
35989
+ if (m.author?.kind === "user" && !m.partial && this.opts.onUserMessage) {
35990
+ const cb = this.opts.onUserMessage;
35991
+ Promise.resolve().then(() => cb(m, this)).catch((e) => this.opts.log(`[chat ${this.chatId}] onUserMessage error: ${e.message}`));
35903
35992
  }
35904
35993
  }
35905
35994
  this.dirtySinceWrite++;
@@ -36046,8 +36135,11 @@ var init_chat_turn = __esm(() => {
36046
36135
  // src/_impl/chat-plan-actions.ts
36047
36136
  async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36048
36137
  const lines = [];
36049
- for (const e of parseErrors)
36138
+ const results = [];
36139
+ for (const e of parseErrors) {
36050
36140
  lines.push(`- [err] plan parse: ${e.message}`);
36141
+ results.push({ type: "parse", status: "error", error: e.message });
36142
+ }
36051
36143
  let actions = actionsIn;
36052
36144
  let truncated = false;
36053
36145
  if (actions.length > PLAN_ACTION_LIMIT2) {
@@ -36062,6 +36154,7 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36062
36154
  counts[a.type] = (counts[a.type] || 0) + 1;
36063
36155
  if (counts[a.type] > cap) {
36064
36156
  lines.push(`- [warn] ${a.type} sub-cap ${cap} hit, dropping extra`);
36157
+ results.push({ type: a.type, status: "note", message: `${a.type} sub-cap ${cap} hit, dropping extra` });
36065
36158
  return false;
36066
36159
  }
36067
36160
  return true;
@@ -36092,6 +36185,7 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36092
36185
  const project_id = a.project_id || ctx.projectId;
36093
36186
  if (!project_id) {
36094
36187
  lines.push(`- [err] create "${a.title}": project_id required (chat has no pinned project)`);
36188
+ results.push({ type: "create", status: "error", error: "project_id required (chat has no pinned project)", label: a.title });
36095
36189
  tally(false);
36096
36190
  continue;
36097
36191
  }
@@ -36108,29 +36202,35 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36108
36202
  const res = await apiClient.post(mutateUrl, body, { headers });
36109
36203
  if (!res.success) {
36110
36204
  lines.push(`- [err] create "${a.title}": ${res.error || res.status}`);
36205
+ results.push({ type: "create", status: "error", error: String(res.error || res.status), label: a.title });
36111
36206
  tally(false);
36112
36207
  continue;
36113
36208
  }
36114
36209
  const created = res.data;
36115
36210
  lines.push(`- [ok] created **${created.key}** - ${created.title}${created.assignee_id ? ` -> @${created.assignee_id}` : ""}`);
36211
+ 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
36212
  tally(true);
36117
36213
  } else if (a.type === "update") {
36118
36214
  const res = await apiClient.post(mutateUrl, { action: "update", ...a }, { headers });
36119
36215
  if (!res.success) {
36120
36216
  lines.push(`- [err] update ${a.id}: ${res.error || res.status}`);
36217
+ results.push({ type: "update", status: "error", error: String(res.error || res.status), label: a.id });
36121
36218
  tally(false);
36122
36219
  continue;
36123
36220
  }
36124
36221
  lines.push(`- [ok] updated ${res.data.key}`);
36222
+ 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
36223
  tally(true);
36126
36224
  } else if (a.type === "delegate") {
36127
36225
  const res = await apiClient.post(mutateUrl, { action: "update", id: a.id, assignee_type: "agent", assignee_id: a.assignee_id, status: "todo" }, { headers });
36128
36226
  if (!res.success) {
36129
36227
  lines.push(`- [err] delegate ${a.id}: ${res.error || res.status}`);
36228
+ results.push({ type: "delegate", status: "error", error: String(res.error || res.status), label: a.id });
36130
36229
  tally(false);
36131
36230
  continue;
36132
36231
  }
36133
36232
  lines.push(`- [ok] delegated ${res.data.key} -> ${a.assignee_id}`);
36233
+ results.push({ type: "delegate", status: "ok", issue_id: res.data.id, key: res.data.key, assignee_id: a.assignee_id });
36134
36234
  tally(true);
36135
36235
  } else if (a.type === "handoff") {
36136
36236
  const res = await apiClient.post(mutateUrl, {
@@ -36143,40 +36243,48 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36143
36243
  }, { headers });
36144
36244
  if (!res.success) {
36145
36245
  lines.push(`- [err] handoff -> ${a.target_agent_id}: ${res.error || res.status}`);
36246
+ results.push({ type: "handoff", status: "error", error: String(res.error || res.status), label: a.target_agent_id });
36146
36247
  tally(false);
36147
36248
  continue;
36148
36249
  }
36149
36250
  lines.push(`- [ok] handoff -> ${a.target_agent_id} via **${res.data?.child_key}** expect=${a.expect ?? "summary"}`);
36251
+ 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
36252
  tally(true);
36151
36253
  } else if (a.type === "issue.comment") {
36152
36254
  const res = await apiClient.post(mutateUrl, { action: "comment", id: a.id, body: a.body }, { headers });
36153
36255
  if (!res.success) {
36154
36256
  lines.push(`- [err] issue.comment ${a.id}: ${res.error || res.status}`);
36257
+ results.push({ type: "issue.comment", status: "error", error: String(res.error || res.status), label: a.id });
36155
36258
  tally(false);
36156
36259
  continue;
36157
36260
  }
36158
36261
  const dispatched = res.data?.dispatched;
36159
36262
  lines.push(`- [ok] commented on ${a.id}${dispatched ? " -> dispatched agent" : ""}`);
36263
+ results.push({ type: "issue.comment", status: "ok", issue_id: a.id, dispatched: !!dispatched });
36160
36264
  tally(true);
36161
36265
  } else if (a.type === "issue.delete") {
36162
36266
  const res = await apiClient.post(mutateUrl, { action: "delete", id: a.id }, { headers });
36163
36267
  if (!res.success) {
36164
36268
  lines.push(`- [err] issue.delete ${a.id}: ${res.error || res.status}`);
36269
+ results.push({ type: "issue.delete", status: "error", error: String(res.error || res.status), label: a.id });
36165
36270
  tally(false);
36166
36271
  continue;
36167
36272
  }
36168
36273
  lines.push(`- [ok] deleted ${res.data?.key || a.id}`);
36274
+ results.push({ type: "issue.delete", status: "ok", key: res.data?.key || a.id, issue_id: res.data?.id || a.id });
36169
36275
  tally(true);
36170
36276
  } else if (a.type === "issue.delete_where") {
36171
36277
  const project_id = a.project_id || ctx.projectId;
36172
36278
  if (!project_id) {
36173
36279
  lines.push(`- [err] issue.delete_where: project_id required`);
36280
+ results.push({ type: "issue.delete_where", status: "error", error: "project_id required" });
36174
36281
  tally(false);
36175
36282
  continue;
36176
36283
  }
36177
36284
  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
36285
  if (!res.success) {
36179
36286
  lines.push(`- [err] issue.delete_where: ${res.error || res.status}`);
36287
+ results.push({ type: "issue.delete_where", status: "error", error: String(res.error || res.status) });
36180
36288
  tally(false);
36181
36289
  continue;
36182
36290
  }
@@ -36185,18 +36293,21 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36185
36293
  lines.push(`- [ok] issue.delete_where: deleted ${count3} issue(s)`);
36186
36294
  for (const r of rows)
36187
36295
  lines.push(` - ${r.key}`);
36296
+ results.push({ type: "issue.delete_where", status: "ok", count: count3, keys: rows.map((r) => r.key) });
36188
36297
  tally(true);
36189
36298
  } else if (a.type === "issue.list") {
36190
36299
  const project_id = a.project_id || ctx.projectId;
36191
36300
  const res = await apiClient.post(queryUrl, { kind: "list", project_id, status: a.status, assignee_id: a.assignee_id, limit: a.limit ?? 20 }, { headers });
36192
36301
  if (!res.success) {
36193
36302
  lines.push(`- [err] issue.list: ${res.error || res.status}`);
36303
+ results.push({ type: "issue.list", status: "error", error: String(res.error || res.status) });
36194
36304
  tally(false);
36195
36305
  continue;
36196
36306
  }
36197
36307
  const rows = Array.isArray(res.data) ? res.data : Array.isArray(res.data?.results) ? res.data.results : [];
36198
36308
  if (!rows.length) {
36199
36309
  lines.push(`- [ok] issue.list: 0 issues`);
36310
+ results.push({ type: "issue.list", status: "ok", count: 0 });
36200
36311
  tally(true);
36201
36312
  continue;
36202
36313
  }
@@ -36204,12 +36315,14 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36204
36315
  for (const r of rows) {
36205
36316
  lines.push(` - **${r.key}** [${r.status}] ${r.title}${r.assignee_id ? ` (@${r.assignee_id})` : ""}`);
36206
36317
  }
36318
+ results.push({ type: "issue.list", status: "ok", count: rows.length });
36207
36319
  tally(true);
36208
36320
  } else if (a.type === "issue.search") {
36209
36321
  const project_id = a.project_id || ctx.projectId;
36210
36322
  const res = await apiClient.post(queryUrl, { kind: "search", project_id, query: a.query, limit: a.limit ?? 10 }, { headers });
36211
36323
  if (!res.success) {
36212
36324
  lines.push(`- [err] issue.search "${a.query}": ${res.error || res.status}`);
36325
+ results.push({ type: "issue.search", status: "error", error: String(res.error || res.status), label: a.query });
36213
36326
  tally(false);
36214
36327
  continue;
36215
36328
  }
@@ -36217,12 +36330,12 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36217
36330
  lines.push(`- [ok] issue.search "${a.query}": ${rows.length} hit(s)`);
36218
36331
  for (const r of rows)
36219
36332
  lines.push(` - **${r.key}** [${r.status}] ${r.title}`);
36333
+ results.push({ type: "issue.search", status: "ok", count: rows.length, query: a.query });
36220
36334
  tally(true);
36221
36335
  } else if (a.type === "agent.create") {
36222
36336
  const res = await apiClient.post(agentsMutateUrl, {
36223
36337
  action: "create",
36224
36338
  name: a.name,
36225
- type: a.agent_type,
36226
36339
  prompt: a.prompt,
36227
36340
  skill_ids: a.skill_ids,
36228
36341
  allowed_tools: a.allowed_tools,
@@ -36230,19 +36343,24 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36230
36343
  }, { headers });
36231
36344
  if (!res.success) {
36232
36345
  lines.push(`- [err] agent.create "${a.name}": ${res.error || res.status}`);
36346
+ results.push({ type: "agent.create", status: "error", error: String(res.error || res.status), label: a.name });
36233
36347
  tally(false);
36234
36348
  continue;
36235
36349
  }
36236
36350
  lines.push(`- [ok] agent.create "${a.name}" -> ${res.data?.agent_id}`);
36351
+ 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
36352
  tally(true);
36238
36353
  } else if (a.type === "agent.update") {
36239
36354
  const res = await apiClient.post(agentsMutateUrl, { action: "update", ...a }, { headers });
36240
36355
  if (!res.success) {
36241
36356
  lines.push(`- [err] agent.update ${a.id}: ${res.error || res.status}`);
36357
+ results.push({ type: "agent.update", status: "error", error: String(res.error || res.status), label: a.id });
36242
36358
  tally(false);
36243
36359
  continue;
36244
36360
  }
36361
+ const changed = ["name", "prompt", "allowed_tools"].filter((k) => a[k] !== undefined);
36245
36362
  lines.push(res.data?.queued ? `- [pending] agent.update ${a.id} queued` : `- [ok] agent.update ${a.id}`);
36363
+ 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
36364
  tally(true);
36247
36365
  } else if (a.type === "skill.create") {
36248
36366
  const res = await apiClient.post(skillsMutateUrl, {
@@ -36255,53 +36373,68 @@ async function executeChatPlanActions(actionsIn, parseErrors, ctx) {
36255
36373
  }, { headers });
36256
36374
  if (!res.success) {
36257
36375
  lines.push(`- [err] skill.create "${a.name}": ${res.error || res.status}`);
36376
+ results.push({ type: "skill.create", status: "error", error: String(res.error || res.status), label: a.name });
36258
36377
  tally(false);
36259
36378
  continue;
36260
36379
  }
36261
36380
  const queued = res.data?.pending_op_id;
36262
36381
  lines.push(queued ? `- [pending] skill.create "${a.name}" queued for human review (op ${queued})` : `- [ok] skill.create "${a.name}" -> ${res.data?.skill_id}`);
36382
+ results.push({ type: "skill.create", status: "ok", pending_op_id: queued, name: a.name, description: a.description });
36263
36383
  tally(true);
36264
36384
  } else if (a.type === "skill.attach" || a.type === "skill.detach") {
36265
36385
  const action = a.type === "skill.attach" ? "attach_skill" : "detach_skill";
36266
36386
  const res = await apiClient.post(agentsMutateUrl, { action, agent_id: a.agent_id, skill_id: a.skill_id }, { headers });
36267
36387
  if (!res.success) {
36268
36388
  lines.push(`- [err] ${a.type} ${a.skill_id}->${a.agent_id}: ${res.error || res.status}`);
36389
+ results.push({ type: a.type, status: "error", error: String(res.error || res.status), label: `${a.skill_id}->${a.agent_id}` });
36269
36390
  tally(false);
36270
36391
  continue;
36271
36392
  }
36272
36393
  lines.push(res.data?.queued ? `- [pending] ${a.type} queued` : `- [ok] ${a.type} ${a.skill_id} <-> ${a.agent_id}`);
36394
+ 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
36395
  tally(true);
36274
36396
  } else if (a.type === "session.create") {
36275
36397
  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
36398
  if (!res.success) {
36277
36399
  lines.push(`- [err] session.create role=${a.role}: ${res.error || res.status}`);
36400
+ results.push({ type: "session.create", status: "error", error: String(res.error || res.status) });
36278
36401
  tally(false);
36279
36402
  continue;
36280
36403
  }
36281
36404
  const sess = res.data?.session;
36282
36405
  const reused = res.data?.reused;
36283
36406
  lines.push(`- [${reused ? "reuse" : "ok"}] session.${reused ? "reuse" : "create"} ${sess?.id?.slice(0, 8) || "?"} (role=${a.role})`);
36407
+ results.push({ type: "session.create", status: "ok", session_id: sess?.id, role: a.role, agent_id: a.agent_id, reused: !!reused });
36284
36408
  tally(true);
36285
36409
  } else if (a.type === "eval.submit") {
36286
36410
  lines.push(`- [err] eval.submit not allowed in chat (only inside an eval dispatch)`);
36411
+ results.push({ type: "eval.submit", status: "error", error: "eval.submit not allowed in chat" });
36287
36412
  tally(false);
36288
36413
  }
36289
36414
  } catch (e) {
36290
36415
  lines.push(`- [err] ${a.type} failed: ${String(e)}`);
36416
+ results.push({ type: a.type, status: "error", error: String(e) });
36291
36417
  tally(false);
36292
36418
  }
36293
36419
  }
36294
- if (truncated)
36420
+ if (truncated) {
36295
36421
  lines.push(`- [warn] action list truncated at ${PLAN_ACTION_LIMIT2}`);
36296
- return { ok, fail: fail12, lines };
36422
+ results.push({ type: "note", status: "note", message: `Action list truncated at ${PLAN_ACTION_LIMIT2}` });
36423
+ }
36424
+ return { ok, fail: fail12, lines, results };
36297
36425
  }
36298
36426
  function renderChatPlanSummary(res) {
36299
36427
  if (!res.lines.length)
36300
36428
  return "";
36429
+ const json2 = JSON.stringify({ version: 1, actions: res.results });
36301
36430
  return `**Planning actions**
36302
36431
 
36303
36432
  ${res.lines.join(`
36304
- `)}`;
36433
+ `)}
36434
+
36435
+ \`\`\`multi-actions
36436
+ ${json2}
36437
+ \`\`\``;
36305
36438
  }
36306
36439
  var PLAN_ACTION_LIMIT2 = 10, SUBCAPS;
36307
36440
  var init_chat_plan_actions = __esm(() => {
@@ -36321,391 +36454,327 @@ var init_chat_plan_actions = __esm(() => {
36321
36454
  // src/_impl/chat-supervisor.ts
36322
36455
  var exports_chat_supervisor = {};
36323
36456
  __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();
36457
+ runChatTurn: () => runChatTurn
36458
+ });
36459
+ async function runChatTurn(opts) {
36460
+ const { apiUrl, authToken: authToken2, workspaceId, chatId, messageId, log: log4 } = opts;
36461
+ const headers = { authorization: `Bearer ${authToken2}` };
36462
+ const metaR = await fetch(`${apiUrl}/api/workspaces/${workspaceId}/chats/${chatId}`, { headers });
36463
+ if (!metaR.ok) {
36464
+ log4(`[chat ${chatId}] meta fetch failed: ${metaR.status}`);
36465
+ return;
36351
36466
  }
36352
- async refresh() {
36353
- if (this.closed)
36467
+ const chat2 = await metaR.json();
36468
+ const peer = new ChatPeer({
36469
+ apiUrl,
36470
+ authToken: authToken2,
36471
+ workspaceId,
36472
+ chatId,
36473
+ primaryAgentId: chat2.primary_agent_id,
36474
+ log: log4
36475
+ });
36476
+ peer.start();
36477
+ try {
36478
+ const msg = await peer.awaitMessage(messageId, 7000);
36479
+ if (!msg) {
36480
+ log4(`[chat ${chatId}] message ${messageId} not visible after sync; aborting`);
36354
36481
  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
36482
  }
36483
+ await processUserMessage(chat2, msg, peer, { apiUrl, authToken: authToken2, workspaceId, log: log4 });
36484
+ } finally {
36485
+ peer.close();
36384
36486
  }
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;
36487
+ }
36488
+ async function fetchAgents(apiUrl, workspaceId, authToken2) {
36489
+ const headers = { authorization: `Bearer ${authToken2}` };
36490
+ try {
36491
+ const r = await fetch(`${apiUrl}/api/workspaces/${workspaceId}/agents`, { headers });
36492
+ if (!r.ok)
36493
+ return [];
36494
+ const body = await r.json();
36495
+ const arr = Array.isArray(body) ? body : Array.isArray(body.results) ? body.results : [];
36496
+ return arr.map((a) => ({ id: a.id, name: a.name }));
36497
+ } catch {
36498
+ return [];
36413
36499
  }
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 {
36500
+ }
36501
+ function resolveAgentFromMention(agents, mentioned) {
36502
+ if (!mentioned)
36503
+ return null;
36504
+ const m = mentioned.toLowerCase();
36505
+ return agents.find((a) => a.name?.toLowerCase() === m || a.id === mentioned) ?? null;
36506
+ }
36507
+ async function resolveCwd(apiUrl, workspaceId, authToken2, chat2) {
36508
+ if (!chat2.project_id || !chat2.device_id)
36509
+ return;
36510
+ const headers = { authorization: `Bearer ${authToken2}` };
36511
+ try {
36512
+ const r = await fetch(`${apiUrl}/api/workspaces/${workspaceId}/projects/${chat2.project_id}/devices`, { headers });
36513
+ if (!r.ok)
36426
36514
  return;
36427
- }
36515
+ const body = await r.json();
36516
+ const row = body.results.find((d) => d.id === chat2.device_id);
36517
+ return row?.working_dir || undefined;
36518
+ } catch {
36519
+ return;
36428
36520
  }
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 {
36521
+ }
36522
+ async function resolveAgentRuntime(apiUrl, workspaceId, authToken2, chat2, agentId) {
36523
+ if (chat2.runtime)
36524
+ return chat2.runtime;
36525
+ if (!agentId || typeof agentId !== "string")
36526
+ return null;
36527
+ const headers = { authorization: `Bearer ${authToken2}` };
36528
+ try {
36529
+ const r = await fetch(`${apiUrl}/api/workspaces/${workspaceId}/agents/${agentId}`, { headers });
36530
+ if (!r.ok)
36442
36531
  return null;
36443
- }
36532
+ const body = await r.json();
36533
+ return body.runtime || body.type || null;
36534
+ } catch {
36535
+ return null;
36444
36536
  }
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 [];
36537
+ }
36538
+ async function processUserMessage(chat2, userMsg, peer, ctx) {
36539
+ const { apiUrl, authToken: authToken2, workspaceId, log: log4 } = ctx;
36540
+ log4(`[chat ${chat2.id}] user: ${userMsg.text.slice(0, 80)}`);
36541
+ const allAgents = await fetchAgents(apiUrl, workspaceId, authToken2);
36542
+ const mentioned = userMsg.mentions?.[0];
36543
+ const mentionAgent = resolveAgentFromMention(allAgents, mentioned);
36544
+ const resolvedAgentId = mentionAgent?.id ?? chat2.primary_agent_id ?? null;
36545
+ const agentAuthorId = resolvedAgentId || mentioned || "agent";
36546
+ const runtime4 = await resolveAgentRuntime(apiUrl, workspaceId, authToken2, chat2, resolvedAgentId);
36547
+ const cwd = await resolveCwd(apiUrl, workspaceId, authToken2, chat2);
36548
+ if (cwd)
36549
+ log4(`[chat ${chat2.id}] cwd=${cwd}`);
36550
+ if (mentionAgent)
36551
+ log4(`[chat ${chat2.id}] mention → ${mentionAgent.name}`);
36552
+ let textContainerId = null;
36553
+ let buffered = "";
36554
+ let lastFlush = Date.now();
36555
+ let agentReplyText = "";
36556
+ const toolMsgByCallId = new Map;
36557
+ const flushText = (force = false) => {
36558
+ const now = Date.now();
36559
+ if (!buffered || textContainerId == null)
36560
+ return;
36561
+ if (force || now - lastFlush > 200) {
36562
+ peer.appendPartialText(textContainerId, buffered);
36563
+ buffered = "";
36564
+ lastFlush = now;
36456
36565
  }
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(`
36566
+ };
36567
+ const closeText = () => {
36568
+ if (textContainerId == null)
36569
+ return;
36570
+ flushText(true);
36571
+ peer.finalizePartialMessage(textContainerId);
36572
+ textContainerId = null;
36573
+ };
36574
+ const agentDisplayName = mentionAgent?.name ?? allAgents.find((a) => a.id === resolvedAgentId)?.name ?? runtime4 ?? undefined;
36575
+ const ensureTextOpen = () => {
36576
+ if (textContainerId == null) {
36577
+ textContainerId = peer.beginPartialAgentMessage(agentAuthorId, agentDisplayName).containerId;
36578
+ }
36579
+ };
36580
+ const summarizeInput = (raw) => {
36581
+ if (!raw || typeof raw !== "object")
36582
+ return;
36583
+ const r = raw;
36584
+ const out = {};
36585
+ if (typeof r.file_path === "string")
36586
+ out.file_path = r.file_path;
36587
+ if (typeof r.path === "string")
36588
+ out.path = r.path;
36589
+ if (typeof r.filename === "string")
36590
+ out.filename = r.filename;
36591
+ if (typeof r.command === "string")
36592
+ out.command = String(r.command).slice(0, 400);
36593
+ if (typeof r.pattern === "string")
36594
+ out.pattern = String(r.pattern).slice(0, 200);
36595
+ if (typeof r.query === "string")
36596
+ out.query = String(r.query).slice(0, 200);
36597
+ if (typeof r.url === "string")
36598
+ out.url = r.url;
36599
+ return Object.keys(out).length ? out : undefined;
36600
+ };
36601
+ const summarizeContent = (raw) => {
36602
+ const bytes = raw?.length ?? 0;
36603
+ const lines = raw ? raw.split(`
36529
36604
  `).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" });
36605
+ const trimmed = raw && raw.length > 240 ? raw.slice(0, 240) + "…" : raw || "";
36606
+ return { content: trimmed, bytes, lines };
36607
+ };
36608
+ const mapToolKind = (raw) => {
36609
+ const s = (raw || "").toLowerCase();
36610
+ if (s.includes("edit") || s.includes("modify"))
36611
+ return "edit";
36612
+ if (s.includes("read") || s.includes("view") || s.includes("fetch"))
36613
+ return "read";
36614
+ if (s.includes("write") || s.includes("create"))
36615
+ return "write";
36616
+ if (s.includes("execute") || s.includes("bash") || s.includes("shell") || s.includes("run"))
36617
+ return "bash";
36618
+ if (s.includes("search") || s.includes("grep") || s.includes("find"))
36619
+ return "search";
36620
+ return "other";
36621
+ };
36622
+ const preamble = buildChatPreamble({ projectId: chat2.project_id, agents: allAgents });
36623
+ await new Promise((resolve2) => {
36624
+ handleChatTurn({
36625
+ chatId: chat2.id,
36626
+ prompt: userMsg.text,
36627
+ preferredRuntime: runtime4,
36628
+ cwd,
36629
+ systemPreamble: preamble,
36630
+ log: log4,
36631
+ onChunk: (text) => {
36632
+ ensureTextOpen();
36633
+ buffered += text;
36634
+ agentReplyText += text;
36635
+ flushText();
36636
+ },
36637
+ onToolCall: (ev) => {
36638
+ closeText();
36639
+ const partial2 = ev.status !== "completed" && ev.status !== "failed";
36640
+ const status3 = ev.status || "in_progress";
36641
+ if (ev.id) {
36642
+ const existing = toolMsgByCallId.get(ev.id);
36643
+ if (existing) {
36644
+ peer.patchMessage(existing, {
36645
+ partial: partial2,
36646
+ status: status3,
36647
+ tool: ev.tool,
36648
+ tool_kind: mapToolKind(ev.kind),
36649
+ input: summarizeInput(ev.input)
36650
+ });
36651
+ return;
36624
36652
  }
36625
- this.opts.log(`[chat ${chat2.id}] done: ${stopReason}`);
36626
- resolve2();
36627
36653
  }
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
36654
+ const id3 = `tc_${ev.id || Math.random().toString(36).slice(2)}`;
36655
+ const containerId = peer.appendStructured({
36656
+ id: id3,
36657
+ author: { kind: "agent", id: agentAuthorId, name: agentDisplayName },
36658
+ kind: "tool_call",
36659
+ text: "",
36660
+ ts: Math.floor(Date.now() / 1000),
36661
+ mentions: [],
36662
+ attachments: [],
36663
+ partial: partial2,
36664
+ tool: ev.tool,
36665
+ tool_kind: mapToolKind(ev.kind),
36666
+ status: status3,
36667
+ input: summarizeInput(ev.input),
36668
+ tool_call_id: ev.id
36640
36669
  });
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
- });
36670
+ if (ev.id)
36671
+ toolMsgByCallId.set(ev.id, containerId);
36672
+ },
36673
+ onToolResult: (ev) => {
36674
+ closeText();
36675
+ const summary5 = summarizeContent(ev.content);
36676
+ peer.appendStructured({
36677
+ id: `tr_${ev.tool_call_id || Math.random().toString(36).slice(2)}_${Date.now()}`,
36678
+ author: { kind: "agent", id: agentAuthorId, name: agentDisplayName },
36679
+ kind: "tool_result",
36680
+ text: `${summary5.lines} lines · ${summary5.bytes} bytes`,
36681
+ ts: Math.floor(Date.now() / 1000),
36682
+ mentions: [],
36683
+ attachments: [],
36684
+ partial: false,
36685
+ tool_call_id: ev.tool_call_id,
36686
+ content: summary5.content
36687
+ });
36688
+ const cid = ev.tool_call_id ? toolMsgByCallId.get(ev.tool_call_id) : undefined;
36689
+ if (cid)
36690
+ peer.patchMessage(cid, { partial: false, status: "completed" });
36691
+ },
36692
+ onDone: (stopReason) => {
36693
+ closeText();
36694
+ for (const cid of toolMsgByCallId.values()) {
36695
+ peer.patchMessage(cid, { partial: false, status: "completed" });
36653
36696
  }
36654
- this.opts.log(`[chat ${chat2.id}] plan exec: ${res.ok} ok, ${res.fail} fail, ${errors3.length} parse-err`);
36697
+ log4(`[chat ${chat2.id}] done: ${stopReason}`);
36698
+ resolve2();
36655
36699
  }
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}`));
36700
+ });
36701
+ });
36702
+ try {
36703
+ const { actions, errors: errors3 } = parsePlanBlocks(agentReplyText);
36704
+ if (actions.length || errors3.length) {
36705
+ const res = await executeChatPlanActions(actions, errors3, {
36706
+ apiUrl,
36707
+ wsId: workspaceId,
36708
+ chatId: chat2.id,
36709
+ projectId: chat2.project_id,
36710
+ agentId: resolvedAgentId,
36711
+ log: log4
36712
+ });
36713
+ const summary5 = renderChatPlanSummary(res);
36714
+ if (summary5) {
36715
+ peer.appendStructured({
36716
+ id: `sys_plan_${Date.now()}`,
36717
+ author: { kind: "system", id: "multi-plan", name: "multi" },
36718
+ kind: "text",
36719
+ text: summary5,
36720
+ ts: Math.floor(Date.now() / 1000),
36721
+ mentions: [],
36722
+ attachments: [],
36723
+ partial: false
36724
+ });
36725
+ }
36726
+ log4(`[chat ${chat2.id}] plan exec: ${res.ok} ok, ${res.fail} fail, ${errors3.length} parse-err`);
36666
36727
  }
36728
+ const ui = parseUiBlocks(agentReplyText);
36729
+ if (ui.blocks.length)
36730
+ log4(`[chat ${chat2.id}] ui blocks: ${ui.blocks.length}`);
36731
+ if (ui.errors.length)
36732
+ log4(`[chat ${chat2.id}] ui parse err: ${ui.errors.length}`);
36733
+ } catch (e) {
36734
+ log4(`[chat ${chat2.id}] post-turn err: ${e.message}`);
36667
36735
  }
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.
36736
+ if (/^new chat$/i.test(chat2.title) && agentReplyText.trim()) {
36737
+ autoTitle(chat2, userMsg.text, agentReplyText, ctx).catch((e) => log4(`[chat ${chat2.id}] auto-title failed: ${e.message}`));
36738
+ }
36739
+ }
36740
+ async function autoTitle(chat2, userMsg, agentReply, ctx) {
36741
+ const { apiUrl, authToken: authToken2, workspaceId, log: log4 } = ctx;
36742
+ const runtime4 = await resolveAgentRuntime(apiUrl, workspaceId, authToken2, chat2, chat2.primary_agent_id);
36743
+ const trimUser = userMsg.slice(0, 800);
36744
+ const trimAgent = agentReply.slice(0, 1200);
36745
+ const prompt = `Reply with ONLY a 3-6 word title for this conversation. No quotes, no punctuation, no explanation. Capitalize each word.
36673
36746
 
36674
36747
  ` + `User: ${trimUser}
36675
36748
 
36676
36749
  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
- });
36750
+ let collected = "";
36751
+ await new Promise((resolve2) => {
36752
+ handleChatTurn({
36753
+ chatId: `${chat2.id}__title`,
36754
+ prompt,
36755
+ preferredRuntime: runtime4,
36756
+ log: log4,
36757
+ onChunk: (t) => {
36758
+ collected += t;
36759
+ },
36760
+ onDone: () => resolve2()
36689
36761
  });
36690
- const cleaned = collected.split(`
36762
+ });
36763
+ const cleaned = collected.split(`
36691
36764
  `).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
- }
36765
+ const stripped = cleaned.replace(/^["'`*_#-]+|["'`*_#-]+$/g, "").trim();
36766
+ const final = stripped.split(/\s+/).slice(0, 8).join(" ").slice(0, 60);
36767
+ if (!final || /^new chat$/i.test(final))
36768
+ return;
36769
+ try {
36770
+ const r = await fetch(`${apiUrl}/api/workspaces/${workspaceId}/chats/${chat2.id}`, {
36771
+ method: "PATCH",
36772
+ headers: { authorization: `Bearer ${authToken2}`, "content-type": "application/json" },
36773
+ body: JSON.stringify({ title: final })
36774
+ });
36775
+ if (r.ok)
36776
+ log4(`[chat ${chat2.id}] auto-titled: ${final}`);
36777
+ } catch {}
36709
36778
  }
36710
36779
  function buildChatPreamble(args2) {
36711
36780
  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 +36802,7 @@ Action vocabulary:
36733
36802
  {"type":"issue.delete_where","status":"todo","limit":50},
36734
36803
  {"type":"issue.list","status":"todo","limit":20},
36735
36804
  {"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"]},
36805
+ {"type":"agent.create","name":"refactor-bot","prompt":"...","allowed_tools":["Read","Edit","Bash"]},
36737
36806
  {"type":"agent.update","id":"ag_xxx","prompt":"..."},
36738
36807
  {"type":"skill.create","name":"run-tests","description":"...","body":"---\\nname: run-tests\\n---\\n..."},
36739
36808
  {"type":"skill.attach","agent_id":"ag_xxx","skill_id":"sk_yyy"},
@@ -37334,7 +37403,7 @@ import { parseArgs } from "util";
37334
37403
  // package.json
37335
37404
  var package_default = {
37336
37405
  name: "@shipers-dev/multi",
37337
- version: "0.39.2",
37406
+ version: "0.41.0",
37338
37407
  type: "module",
37339
37408
  bin: {
37340
37409
  "multi-agent": "./dist/index.js"
@@ -38765,6 +38834,34 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
38765
38834
  }
38766
38835
  })();
38767
38836
  }
38837
+ if (url2.pathname === "/run-chat-turn" && req.method === "POST") {
38838
+ if (req.headers.get("authorization") !== expectedAuth)
38839
+ return new Response("unauthorized", { status: 401 });
38840
+ return (async () => {
38841
+ try {
38842
+ const body = await req.json();
38843
+ if (!body?.chat_id || !body?.message_id) {
38844
+ return Response.json({ error: "chat_id and message_id required" }, { status: 400 });
38845
+ }
38846
+ if (!cfg.workspaceId || !cfg.authToken || !cfg.deviceId) {
38847
+ return Response.json({ error: "daemon not configured" }, { status: 503 });
38848
+ }
38849
+ const { runChatTurn: runChatTurn2 } = await Promise.resolve().then(() => (init_chat_supervisor(), exports_chat_supervisor));
38850
+ runChatTurn2({
38851
+ apiUrl,
38852
+ authToken: cfg.authToken,
38853
+ workspaceId: cfg.workspaceId,
38854
+ deviceId: cfg.deviceId,
38855
+ chatId: body.chat_id,
38856
+ messageId: body.message_id,
38857
+ log: log3
38858
+ }).catch((e) => log3(`[chat ${body.chat_id}] runChatTurn error: ${e.message}`));
38859
+ return Response.json({ accepted: true }, { status: 202 });
38860
+ } catch (e) {
38861
+ return Response.json({ error: String(e) }, { status: 400 });
38862
+ }
38863
+ })();
38864
+ }
38768
38865
  if (url2.pathname === "/stop" && req.method === "POST") {
38769
38866
  if (req.headers.get("authorization") !== expectedAuth)
38770
38867
  return new Response("unauthorized", { status: 401 });
@@ -38826,19 +38923,6 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
38826
38923
  catch: () => new DaemonError({ message: "materializeBundle skipped" })
38827
38924
  }).pipe(exports_Effect.catchAll(() => exports_Effect.void));
38828
38925
  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
38926
  if (cfg.workspaceId) {
38843
38927
  yield* exports_Effect.forkIn(daemonScope)(exports_Effect.tryPromise({
38844
38928
  try: () => drainOfflineDispatches(apiUrl, cfg.deviceId, cfg.workspaceId, cfg.dispatchSecret, db2, () => {
@@ -38928,9 +39012,6 @@ var daemonProgram = ({ cfg, apiUrl }) => exports_Effect.gen(function* () {
38928
39012
  try {
38929
39013
  server.stop();
38930
39014
  } catch {}
38931
- try {
38932
- chatSup?.close();
38933
- } catch {}
38934
39015
  try {
38935
39016
  tunnel?.child?.kill();
38936
39017
  } catch {}