@shipers-dev/multi 0.7.0 → 0.8.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.
Files changed (3) hide show
  1. package/dist/index.js +357 -401
  2. package/package.json +1 -1
  3. package/src/index.ts +12 -53
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipers-dev/multi",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "multi-agent": "./dist/index.js"
package/src/index.ts CHANGED
@@ -17,7 +17,7 @@ const LOG_PATH = join(MULTI_DIR, 'logs', 'agent.log');
17
17
  const SKILLS_DIR = join(MULTI_DIR, 'skills');
18
18
  const STOP_PATH = join(MULTI_DIR, 'stop.flag');
19
19
  const TASKS_DB_PATH = join(MULTI_DIR, 'tasks.db');
20
- const VERSION = '0.7.0';
20
+ const VERSION = '0.8.0';
21
21
 
22
22
  const COMMANDS = {
23
23
  setup: 'Register this device with a workspace',
@@ -257,28 +257,6 @@ async function cmdConnect(apiUrl: string, config: Config) {
257
257
  let workerWake: (() => void) | null = null;
258
258
  const notifyWorker = () => { try { workerWake?.(); workerWake = null; } catch {} };
259
259
 
260
- // Agents linked to this device (refreshed periodically). Used to short-circuit
261
- // server dispatch when an agent-created child issue is assigned to an agent
262
- // running on the same runtime.
263
- const localAgentIds = new Set<string>();
264
- const refreshLocalAgents = async () => {
265
- try {
266
- const res = await apiClient.get<any>(`${apiUrl}/api/devices/${config.deviceId}/agents`);
267
- const rows = res.data?.results || res.data || [];
268
- if (Array.isArray(rows)) {
269
- localAgentIds.clear();
270
- for (const a of rows) if (a?.id) localAgentIds.add(a.id);
271
- }
272
- } catch {}
273
- };
274
- await refreshLocalAgents();
275
-
276
- const enqueueLocal = (task: any) => {
277
- const taskId = task?.issue_id ? `${task.issue_id}-${Date.now()}` : crypto.randomUUID();
278
- db.run('INSERT INTO tasks (id, status, payload) VALUES (?, ?, ?)', [taskId, 'pending', JSON.stringify(task)]);
279
- notifyWorker();
280
- return taskId;
281
- };
282
260
 
283
261
  // Local HTTP server on a free port
284
262
  const port = await pickFreePort();
@@ -351,7 +329,7 @@ async function cmdConnect(apiUrl: string, config: Config) {
351
329
  db.run("UPDATE tasks SET status = 'running', started_at = unixepoch(), attempts = attempts + 1 WHERE id = ?", [row.id]);
352
330
  try {
353
331
  const task = JSON.parse(row.payload);
354
- await handleRunTask(apiUrl, config.deviceId!, task, detected, { localAgentIds, enqueueLocal, refreshLocalAgents });
332
+ await handleRunTask(apiUrl, config.deviceId!, task, detected, {});
355
333
  db.run("UPDATE tasks SET status = 'done', finished_at = unixepoch() WHERE id = ?", [row.id]);
356
334
  } catch (e) {
357
335
  log(`task ${row.id} error: ${String(e)}`);
@@ -430,9 +408,7 @@ async function parseTunnelUrl(stream: ReadableStream<Uint8Array>): Promise<strin
430
408
  }
431
409
 
432
410
  interface RuntimeCtx {
433
- localAgentIds: Set<string>;
434
- enqueueLocal: (task: any) => string;
435
- refreshLocalAgents: () => Promise<void>;
411
+ // Reserved for future runtime-scoped helpers.
436
412
  }
437
413
 
438
414
  async function handleRunTask(apiUrl: string, deviceId: string, task: any, detected: { type: string; path: string }[], ctx?: RuntimeCtx) {
@@ -651,13 +627,13 @@ async function handleRunTask(apiUrl: string, deviceId: string, task: any, detect
651
627
 
652
628
  try {
653
629
  if (useAcp) {
654
- const base = `${task.title}\n\n${task.description || ''}`.trim();
630
+ const issueContext = `## Issue ${task.key}: ${task.title}${task.description ? `\n\n${task.description}` : ''}`;
655
631
  let userPart: string;
656
632
  if (task.followup) {
657
633
  const cleanFollowup = stripSelfMention(task.followup, preferType);
658
- userPart = `# New user message — THIS is the current request\n\n${cleanFollowup}\n\n---\n*Earlier thread (${task.key} "${task.title}") is only background context. Do not re-address the original task unless the new message explicitly refers back to it.*`;
634
+ userPart = `${issueContext}\n\n---\n\n## New user message — current request\n\n${cleanFollowup}`;
659
635
  } else {
660
- userPart = stripSelfMention(base || task.title, preferType);
636
+ userPart = stripSelfMention(issueContext, preferType);
661
637
  }
662
638
  if (attachmentRefs.length) {
663
639
  const lines = attachmentRefs.map(a => `- ${a.filename}: ${a.path}`).join('\n');
@@ -731,13 +707,13 @@ async function handleRunTask(apiUrl: string, deviceId: string, task: any, detect
731
707
  }
732
708
  } catch {}
733
709
  preamble += await buildPlanningPreamble(apiUrl, task);
734
- const base = `${task.title}\n\n${task.description || ''}`.trim();
710
+ const issueContext = `## Issue ${task.key}: ${task.title}${task.description ? `\n\n${task.description}` : ''}`;
735
711
  let userPart: string;
736
712
  if (task.followup) {
737
713
  const cleanFollowup = stripSelfMention(task.followup, preferType);
738
- userPart = `# New user message — THIS is the current request\n\n${cleanFollowup}\n\n---\n*Earlier thread (${task.key} "${task.title}") is only background context. Do not re-address the original task unless the new message explicitly refers back to it.*`;
714
+ userPart = `${issueContext}\n\n---\n\n## New user message — current request\n\n${cleanFollowup}`;
739
715
  } else {
740
- userPart = stripSelfMention(base || task.title, preferType);
716
+ userPart = stripSelfMention(issueContext, preferType);
741
717
  }
742
718
  if (attachmentRefs.length) {
743
719
  const lines = attachmentRefs.map(a => `- ${a.filename}: ${a.path}`).join('\n');
@@ -876,7 +852,7 @@ function extractPlanActions(text: string): PlanAction[] {
876
852
 
877
853
  const PLAN_ACTION_LIMIT = 10;
878
854
 
879
- async function executePlanActions(apiUrl: string, parentTask: any, actions: PlanAction[], ctx: RuntimeCtx): Promise<string> {
855
+ async function executePlanActions(apiUrl: string, parentTask: any, actions: PlanAction[], _ctx: RuntimeCtx): Promise<string> {
880
856
  const lines: string[] = [];
881
857
  let truncated = false;
882
858
  if (actions.length > PLAN_ACTION_LIMIT) {
@@ -884,7 +860,7 @@ async function executePlanActions(apiUrl: string, parentTask: any, actions: Plan
884
860
  actions = actions.slice(0, PLAN_ACTION_LIMIT);
885
861
  }
886
862
  // Prevent agent recursion: a child turn's plan cannot itself spawn more children.
887
- // `planning_depth` is propagated from parent task; depth >= 1 blocks create/delegate.
863
+ // `planning_depth` is carried on each dispatched task (set server-side from issue row).
888
864
  const depth = typeof parentTask.planning_depth === 'number' ? parentTask.planning_depth : 0;
889
865
  if (depth >= 1) {
890
866
  const blocked = actions.filter(a => a.type === 'create' || a.type === 'delegate').length;
@@ -916,16 +892,7 @@ async function executePlanActions(apiUrl: string, parentTask: any, actions: Plan
916
892
  const res = await apiClient.post<any>(`${apiUrl}/api/issues/agent/mutate`, body, { headers });
917
893
  if (!res.success) { lines.push(`- ❌ create "${a.title}": ${res.error || res.status}`); continue; }
918
894
  const created = res.data;
919
- lines.push(`- ✓ created **${created.key}** — ${created.title}${created.assignee_id ? ` → @${created.assignee_id}` : ''}`);
920
- // Same-runtime shortcut: if new issue's agent is linked locally, enqueue directly.
921
- if (created.assignee_type === 'agent' && created.assignee_id && ctx.localAgentIds.has(created.assignee_id)) {
922
- ctx.enqueueLocal({
923
- issue_id: created.id, key: created.key, title: created.title, description: created.description,
924
- agent_id: created.assignee_id, session_id: null, working_dir: parentTask.working_dir || null,
925
- planning_depth: depth + 1,
926
- });
927
- lines.push(` ↳ dispatched locally (same runtime)`);
928
- }
895
+ lines.push(`- ✓ created **${created.key}** — ${created.title}${created.assignee_id ? ` → @${created.assignee_id}` : ''} (autonomy=${created.autonomy_level || 'ask'})`);
929
896
  } else if (a.type === 'update') {
930
897
  const res = await apiClient.post<any>(`${apiUrl}/api/issues/agent/mutate`, { action: 'update', ...a }, { headers });
931
898
  if (!res.success) { lines.push(`- ❌ update ${a.id}: ${res.error || res.status}`); continue; }
@@ -934,14 +901,6 @@ async function executePlanActions(apiUrl: string, parentTask: any, actions: Plan
934
901
  const res = await apiClient.post<any>(`${apiUrl}/api/issues/agent/mutate`, { action: 'update', id: a.id, assignee_type: 'agent', assignee_id: a.assignee_id, status: 'todo' }, { headers });
935
902
  if (!res.success) { lines.push(`- ❌ delegate ${a.id}: ${res.error || res.status}`); continue; }
936
903
  lines.push(`- ✓ delegated ${res.data.key} → ${a.assignee_id}`);
937
- if (ctx.localAgentIds.has(a.assignee_id)) {
938
- ctx.enqueueLocal({
939
- issue_id: res.data.id, key: res.data.key, title: res.data.title, description: res.data.description,
940
- agent_id: a.assignee_id, session_id: res.data.session_id || null, working_dir: parentTask.working_dir || null,
941
- planning_depth: depth + 1,
942
- });
943
- lines.push(` ↳ dispatched locally (same runtime)`);
944
- }
945
904
  }
946
905
  } catch (e) {
947
906
  lines.push(`- ❌ ${a.type} failed: ${String(e)}`);