@shipers-dev/multi 0.48.0 → 0.49.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 (2) hide show
  1. package/dist/index.js +170 -6
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -33210,6 +33210,8 @@ async function runAcp(opts) {
33210
33210
  let activeSessionId = opts.sessionId || null;
33211
33211
  let recording = false;
33212
33212
  let chunkCount = 0;
33213
+ let capturingSummary = false;
33214
+ let summaryBuf = "";
33213
33215
  const alwaysAllow = new Set;
33214
33216
  const client = {
33215
33217
  async sessionUpdate(params) {
@@ -33304,7 +33306,23 @@ async function runAcp(opts) {
33304
33306
  await opts.onEvent({ event_type: "error", payload: { message: `agent produced no output (stopReason=${stopReason})` } });
33305
33307
  }
33306
33308
  await opts.onEvent({ event_type: "result", payload: { stopReason } });
33307
- return { stopReason, sessionId: activeSessionId };
33309
+ let summaryText;
33310
+ if (opts.summaryPrompt && chunkCount > 0) {
33311
+ try {
33312
+ capturingSummary = true;
33313
+ summaryBuf = "";
33314
+ await conn.prompt({
33315
+ sessionId: activeSessionId,
33316
+ prompt: [{ type: "text", text: opts.summaryPrompt }]
33317
+ });
33318
+ summaryText = summaryBuf.trim() || undefined;
33319
+ } catch (e) {
33320
+ await opts.onEvent({ event_type: "progress", payload: { message: `memory summary failed: ${fmtErr(e)}` } });
33321
+ } finally {
33322
+ capturingSummary = false;
33323
+ }
33324
+ }
33325
+ return { stopReason, sessionId: activeSessionId, summaryText };
33308
33326
  } finally {
33309
33327
  try {
33310
33328
  child.kill();
@@ -33313,6 +33331,14 @@ async function runAcp(opts) {
33313
33331
  async function handleSessionUpdate(params, o) {
33314
33332
  const u = params.update;
33315
33333
  const kind = u.sessionUpdate;
33334
+ if (capturingSummary) {
33335
+ if (kind === "agent_message_chunk") {
33336
+ const text = extractText(u.content);
33337
+ if (text)
33338
+ summaryBuf += text;
33339
+ }
33340
+ return;
33341
+ }
33316
33342
  switch (kind) {
33317
33343
  case "agent_message_chunk": {
33318
33344
  const text = extractText(u.content);
@@ -33464,6 +33490,94 @@ function dlog(msg) {
33464
33490
  `);
33465
33491
  } catch {}
33466
33492
  }
33493
+ async function spawnAcpxPrompt(agentType, cwd, sessionName, prompt, collectAssistantText, forward, onPlanUpdate) {
33494
+ const args2 = ["acpx", "--format", "json", "--json-strict", "--approve-all", "--ttl", "0"];
33495
+ if (cwd)
33496
+ args2.push("--cwd", cwd);
33497
+ args2.push(agentType);
33498
+ if (sessionName)
33499
+ args2.push("prompt", "-s", sessionName);
33500
+ else
33501
+ args2.push("prompt");
33502
+ args2.push(prompt);
33503
+ dlog(`[acpx] prompt: ${args2.slice(0, 10).join(" ")} ... (prompt len=${prompt.length})`);
33504
+ const proc = Bun.spawn(args2, { stdout: "pipe", stderr: "pipe", stdin: "ignore" });
33505
+ let stopReason = "end_turn";
33506
+ (async () => {
33507
+ try {
33508
+ const r = proc.stderr.getReader();
33509
+ const d = new TextDecoder;
33510
+ let sb = "";
33511
+ while (true) {
33512
+ const { value: value3, done: done9 } = await r.read();
33513
+ if (done9)
33514
+ break;
33515
+ sb += d.decode(value3, { stream: true });
33516
+ let nl;
33517
+ while ((nl = sb.indexOf(`
33518
+ `)) !== -1) {
33519
+ const ln = sb.slice(0, nl).trim();
33520
+ sb = sb.slice(nl + 1);
33521
+ if (ln)
33522
+ dlog(`[acpx stderr] ${ln.slice(0, 500)}`);
33523
+ }
33524
+ }
33525
+ } catch {}
33526
+ })();
33527
+ const fakeOpts = { agentType, prompt, cwd, sessionName, onEvent: () => {}, onPlanUpdate };
33528
+ const reader = proc.stdout.getReader();
33529
+ const dec = new TextDecoder;
33530
+ let buf = "";
33531
+ while (true) {
33532
+ const { value: value3, done: done9 } = await reader.read();
33533
+ if (done9)
33534
+ break;
33535
+ buf += dec.decode(value3, { stream: true });
33536
+ let nl;
33537
+ while ((nl = buf.indexOf(`
33538
+ `)) !== -1) {
33539
+ const line = buf.slice(0, nl).trim();
33540
+ buf = buf.slice(nl + 1);
33541
+ if (!line)
33542
+ continue;
33543
+ dlog(`[acpx stdout] ${line.slice(0, 500)}`);
33544
+ const events = parseAcpLine(line, (r) => {
33545
+ stopReason = r;
33546
+ }, fakeOpts);
33547
+ for (const ev of events) {
33548
+ if (collectAssistantText && ev.event_type === "assistant_text") {
33549
+ const t = ev.payload?.text;
33550
+ if (typeof t === "string")
33551
+ collectAssistantText.buf += t;
33552
+ } else {
33553
+ await forward(ev);
33554
+ }
33555
+ }
33556
+ }
33557
+ }
33558
+ if (buf.trim()) {
33559
+ const events = parseAcpLine(buf.trim(), (r) => {
33560
+ stopReason = r;
33561
+ }, fakeOpts);
33562
+ for (const ev of events) {
33563
+ if (collectAssistantText && ev.event_type === "assistant_text") {
33564
+ const t = ev.payload?.text;
33565
+ if (typeof t === "string")
33566
+ collectAssistantText.buf += t;
33567
+ } else {
33568
+ await forward(ev);
33569
+ }
33570
+ }
33571
+ }
33572
+ const code = await proc.exited;
33573
+ if (code !== 0 && code !== 130) {
33574
+ if (!collectAssistantText)
33575
+ await forward({ event_type: "error", payload: { message: `acpx exited with code ${code}` } });
33576
+ else
33577
+ dlog(`[acpx] summary spawn exit=${code}`);
33578
+ }
33579
+ return { stopReason };
33580
+ }
33467
33581
  async function runAcpx(opts) {
33468
33582
  const args2 = ["acpx", "--format", "json", "--json-strict", "--approve-all", "--ttl", "0"];
33469
33583
  if (opts.cwd)
@@ -33550,7 +33664,17 @@ async function runAcpx(opts) {
33550
33664
  if (code !== 0 && code !== 130) {
33551
33665
  await opts.onEvent({ event_type: "error", payload: { message: `acpx exited with code ${code}` } });
33552
33666
  }
33553
- return { stopReason };
33667
+ let summaryText;
33668
+ if (opts.summaryPrompt && opts.sessionName && (code === 0 || code === 130)) {
33669
+ const collect = { buf: "" };
33670
+ try {
33671
+ await spawnAcpxPrompt(opts.agentType, opts.cwd, opts.sessionName, opts.summaryPrompt, collect, () => {}, opts.onPlanUpdate);
33672
+ summaryText = collect.buf.trim() || undefined;
33673
+ } catch (e) {
33674
+ dlog(`[acpx] summary error: ${String(e)}`);
33675
+ }
33676
+ }
33677
+ return { stopReason, summaryText };
33554
33678
  }
33555
33679
  function parseAcpLine(line, setStop, opts) {
33556
33680
  let msg;
@@ -34085,7 +34209,6 @@ var init_chat = __esm(() => {
34085
34209
  runtime: exports_external.string().nullable().optional()
34086
34210
  });
34087
34211
  });
34088
-
34089
34212
  // ../lib/index.ts
34090
34213
  var init_lib = __esm(() => {
34091
34214
  init_streams();
@@ -34431,6 +34554,38 @@ function resolveEnforcementMode(task) {
34431
34554
  return m;
34432
34555
  return "enforce";
34433
34556
  }
34557
+ function buildMemorySummaryPrompt(task) {
34558
+ const key = task?.key || task?.issue_id || "task";
34559
+ const title = task?.title || "";
34560
+ return [
34561
+ `The task "${key}: ${title}" is now complete.`,
34562
+ `Provide a memory summary for future agents working on this project.`,
34563
+ `Output EXACTLY 5 short bullets (one line each, prefix "- "):`,
34564
+ `1. What changed (the actual outcome)`,
34565
+ `2. Why (motivation, constraints)`,
34566
+ `3. Key decisions / tradeoffs`,
34567
+ `4. Files or modules touched`,
34568
+ `5. Open follow-ups, if any`,
34569
+ ``,
34570
+ `Rules: do NOT call any tools. Reply with ONLY the 5 bullets, no preamble, no closing.`
34571
+ ].join(`
34572
+ `);
34573
+ }
34574
+ async function writeTaskMemory(apiUrl, task, text) {
34575
+ if (!task?.project_id || !text)
34576
+ return;
34577
+ try {
34578
+ await apiClient.post(`${apiUrl}/api/memory/write`, {
34579
+ project_id: task.project_id,
34580
+ source_kind: "task",
34581
+ source_id: String(task.issue_id || task.id || ""),
34582
+ agent_id: task.agent_id ?? null,
34583
+ text: text.slice(0, 19500)
34584
+ });
34585
+ } catch (e) {
34586
+ log3(`memory write failed: ${String(e)}`);
34587
+ }
34588
+ }
34434
34589
  async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
34435
34590
  const issueId = task.issue_id;
34436
34591
  const isFollowup = !!task.followup;
@@ -34513,6 +34668,7 @@ async function handleRunTask(apiUrl, deviceId, task, detected, ctx) {
34513
34668
  let hadError = false;
34514
34669
  let hasAssistantText = false;
34515
34670
  let liveCommentPromise = null;
34671
+ let memorySummary = null;
34516
34672
  const ensureLiveComment = () => {
34517
34673
  if (liveCommentId)
34518
34674
  return Promise.resolve();
@@ -34821,7 +34977,7 @@ ${userPart}` : userPart;
34821
34977
  throw new Error(`ACP adapter for ${chosen.type} not found`);
34822
34978
  log3(` adapter: ${chosen.type} → ${adapterBin.join(" ")}`);
34823
34979
  const startedAt = Date.now();
34824
- const { sessionId } = await runAcp({
34980
+ const { sessionId, summaryText: acpSummary } = await runAcp({
34825
34981
  apiUrl,
34826
34982
  issueId,
34827
34983
  deviceId,
@@ -34833,6 +34989,7 @@ ${userPart}` : userPart;
34833
34989
  adapterBin,
34834
34990
  autonomy: task.autonomy_level,
34835
34991
  cwd: workingDir,
34992
+ summaryPrompt: buildMemorySummaryPrompt(task),
34836
34993
  onEvent: eventHandler,
34837
34994
  onSession: async (sid) => {
34838
34995
  try {
@@ -34857,6 +35014,7 @@ ${userPart}` : userPart;
34857
35014
  });
34858
35015
  postStream(apiUrl, issueId, "run_finished", { stopReason: typeof sessionId === "string" ? "ok" : "unknown", duration_ms: Date.now() - startedAt });
34859
35016
  log3(` acp session ${sessionId.slice(0, 8)}`);
35017
+ memorySummary = acpSummary || null;
34860
35018
  } else if (useAcpx) {
34861
35019
  let preamble = "";
34862
35020
  try {
@@ -34926,11 +35084,12 @@ Write generated files to: ${outDir}`;
34926
35084
  ${userPart}` : userPart;
34927
35085
  log3(` acpx runner: ${preferType}`);
34928
35086
  const acpxStartedAt = Date.now();
34929
- await runAcpx({
35087
+ const { summaryText: acpxSummary } = await runAcpx({
34930
35088
  agentType: preferType,
34931
35089
  prompt: full,
34932
35090
  cwd: workingDir,
34933
35091
  sessionName: `issue-${issueId}`,
35092
+ summaryPrompt: buildMemorySummaryPrompt(task),
34934
35093
  onEvent: eventHandler,
34935
35094
  onSpawn: (child) => {
34936
35095
  if (ctx?.runEntry) {
@@ -34941,6 +35100,7 @@ ${userPart}` : userPart;
34941
35100
  }
34942
35101
  });
34943
35102
  postStream(apiUrl, issueId, "run_finished", { stopReason: "ok", duration_ms: Date.now() - acpxStartedAt });
35103
+ memorySummary = acpxSummary || null;
34944
35104
  } else {
34945
35105
  const runner = pickRunner(detected, preferType);
34946
35106
  for await (const event of runner(task))
@@ -34984,6 +35144,10 @@ ${userPart}` : userPart;
34984
35144
  if (ISSUE_BASE)
34985
35145
  await apiClient.post(`${ISSUE_BASE}/complete`, {});
34986
35146
  log3(` ✓ ${task.key} complete`);
35147
+ if (memorySummary) {
35148
+ await writeTaskMemory(apiUrl, task, memorySummary);
35149
+ log3(` \uD83D\uDCDD memory summary written (${memorySummary.length} chars)`);
35150
+ }
34987
35151
  if (baseWorkingDir) {
34988
35152
  const mergeTarget = task.merge_to_main === true || task.merge_to_main === "true" ? "main" : typeof task.merge_target === "string" ? task.merge_target : null;
34989
35153
  await exports_Effect.runPromise(terminalGitFlowE(apiUrl, issueId, baseWorkingDir, task.key || issueId, resolveEnforcementMode(task), mergeTarget));
@@ -37798,7 +37962,7 @@ import { parseArgs } from "util";
37798
37962
  // package.json
37799
37963
  var package_default = {
37800
37964
  name: "@shipers-dev/multi",
37801
- version: "0.48.0",
37965
+ version: "0.49.0",
37802
37966
  type: "module",
37803
37967
  bin: {
37804
37968
  "multi-agent": "./dist/index.js"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shipers-dev/multi",
3
- "version": "0.48.0",
3
+ "version": "0.49.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "multi-agent": "./dist/index.js"