spacecode 2.11.0 → 2.11.1

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/repl.mjs CHANGED
@@ -4088,7 +4088,7 @@ var init_defaults_core_loop = __esm({
4088
4088
  body: `You are the core of a coding run. You direct; you never read files, search, run commands or write code. Workers do that.
4089
4089
 
4090
4090
  ## Tools (the only ones that exist)
4091
- - edit_plan {tasks}: add tasks. Each task has id, role, goal (what the worker must do, concrete and self-contained: the worker sees only this, the files and the acceptance), files (a file belongs to one open task at a time), acceptance {kind: checks|rubric|none, detail}, budgetTokens, deadlineMs.
4091
+ - edit_plan {tasks}: add tasks. Each task has id, role, goal (what the worker must do, concrete and self-contained: the worker sees only this, the files and the acceptance), files (a file belongs to one open task at a time), acceptance {kind: checks|rubric|none, detail} (a code task that the repo can check, with tests or a typecheck, uses kind checks so the repo's own typecheck and tests must pass; rubric and none skip them), budgetTokens, deadlineMs (milliseconds; a worker needs minutes to clone, work and check, so use 600000 for a code task; values are clamped to 600000-840000).
4092
4092
  - dispatch_task {taskId}: start a pending task (at most 2 attempts per task).
4093
4093
  - accept_task {taskId}: accept a task in review. A checks task needs a passing verifier first.
4094
4094
  - drop_task {taskId, reason}: drop a task you no longer need.
@@ -4101,7 +4101,7 @@ var init_defaults_core_loop = __esm({
4101
4101
  - When the token budget is exhausted, package what is accepted and send the result.
4102
4102
  - Prefer small tasks with one owner per file. Stay inside the budget.
4103
4103
  - You are only woken by worker results and user messages, never by your own actions. So in the same answer as edit_plan, dispatch_task every task that can start now; a task left pending with nothing running stalls the run.
4104
- - Tasks that must follow another (a test after its implementation) are dispatched after the earlier one is accepted.
4104
+ - Tasks that must follow another (a test after its implementation) are dispatched after the earlier one is accepted; the later worker starts from the accepted work, so give it only its own files and do not repeat the earlier task's work.
4105
4105
  - Worker digests are already included in what you are shown; there is nothing more to read. Decide from them (accept_task, drop_task, or dispatch a retry).
4106
4106
  - Make every tool call of your answer at once; you do not see results between calls. Reply with tool calls only.`
4107
4107
  },
@@ -104407,7 +104407,9 @@ async function runCoreTurn(d, prompt) {
104407
104407
  let lastText = "";
104408
104408
  const say = (content) => {
104409
104409
  lastText = content;
104410
- d.emit({ type: "text", seq: ++seq, content });
104410
+ d.emit({ type: "text", seq: ++seq, content: `${content}
104411
+
104412
+ ` });
104411
104413
  };
104412
104414
  const call = async (method, path13, body) => {
104413
104415
  const res = await d.fetch(`${d.backendUrl}/api/core/runs${path13}`, {
@@ -104433,23 +104435,21 @@ async function runCoreTurn(d, prompt) {
104433
104435
  const chat = await call("POST", `/${runId}/chat`, { id: d.newId(), text: prompt });
104434
104436
  if (chat.reply) say(chat.reply.text);
104435
104437
  else if (chat.replyError) say("(The quick reply is unavailable; the planner has your message.)");
104436
- let after = 0;
104437
- let known = {};
104438
- const patches = /* @__PURE__ */ new Map();
104438
+ const cur = d.cursor;
104439
104439
  const startedAt = d.now();
104440
104440
  const maxWait = d.maxWaitMs ?? DEFAULT_MAX_WAIT_MS;
104441
104441
  let delay = 1e3;
104442
104442
  let state = null;
104443
104443
  for (; ; ) {
104444
104444
  if (d.signal.aborted) throw new CoreTurnError("aborted");
104445
- const events = (await call("GET", `/${runId}/events?after=${after}&limit=200`)).events;
104445
+ const events = (await call("GET", `/${runId}/events?after=${cur.after}&limit=200`)).events;
104446
104446
  for (const e of events) {
104447
- after = Math.max(after, e.seq);
104447
+ cur.after = Math.max(cur.after, e.seq);
104448
104448
  if (!e.applied) continue;
104449
104449
  const p = e.payload ?? {};
104450
104450
  if (e.type === "core_message" && typeof p.text === "string") say(p.text);
104451
104451
  if (e.type === "worker_done" && typeof p.taskId === "string") {
104452
- if (typeof p.patchKey === "string") patches.set(p.taskId, p.patchKey);
104452
+ if (typeof p.patchKey === "string") cur.patches[p.taskId] = p.patchKey;
104453
104453
  if (Array.isArray(p.problems) || typeof p.reason === "string") say(`task ${p.taskId}: the worker's attempt failed${typeof p.reason === "string" ? ` (${p.reason})` : ""}`);
104454
104454
  }
104455
104455
  if (e.type === "verifier_result" && Array.isArray(p.problems) && p.problems.length > 0) say(`task ${String(p.taskId)}: verifier: ${p.problems.join("; ")}`);
@@ -104457,9 +104457,9 @@ async function runCoreTurn(d, prompt) {
104457
104457
  state = (await call("GET", `/${runId}`)).state;
104458
104458
  const now = {};
104459
104459
  for (const id of state.order) now[id] = state.tasks[id].status;
104460
- const changed = state.order.filter((id) => now[id] !== known[id]);
104460
+ const changed = state.order.filter((id) => now[id] !== cur.known[id]);
104461
104461
  if (changed.length > 0) say(changed.map((id) => `task ${id}: ${now[id]}`).join(", "));
104462
- known = now;
104462
+ cur.known = now;
104463
104463
  if (TERMINAL.has(state.status)) break;
104464
104464
  if (d.now() - startedAt > maxWait) throw new CoreTurnError("the run is still working after the wait limit; ask again later or check on it");
104465
104465
  delay = events.length > 0 ? 1e3 : Math.min(5e3, Math.round(delay * 1.5));
@@ -104467,22 +104467,29 @@ async function runCoreTurn(d, prompt) {
104467
104467
  }
104468
104468
  if (state.status === "core_failed") say('The planner failed on every model route. Say "continue" after a moment to resume this run.');
104469
104469
  if (state.status === "stopped") say("The run stopped before everything was accepted.");
104470
+ let patchFailures = 0;
104470
104471
  for (const id of state.order) {
104471
- if (state.tasks[id].status !== "accepted" || !patches.has(id)) continue;
104472
+ if (state.tasks[id].status !== "accepted" || !(id in cur.patches) || cur.applied.includes(id)) continue;
104472
104473
  const art = await d.fetch(`${d.backendUrl}/api/core/runs/${runId}/artifacts/${encodeURIComponent(id)}`, { headers: { authorization: `Bearer ${d.apiKey}` }, signal: d.signal });
104473
104474
  if (!art.ok) {
104474
104475
  say(`task ${id}: its patch could not be fetched (${art.status})`);
104476
+ patchFailures++;
104475
104477
  continue;
104476
104478
  }
104479
+ cur.applied.push(id);
104477
104480
  const result = await d.applyPatch({ taskId: id, patch: await art.text() });
104478
104481
  say(`task ${id}: ${result.applied ? "patch applied" : "patch not applied"} (${result.detail})`);
104482
+ if (!result.applied) patchFailures++;
104479
104483
  }
104480
- return { success: state.status === "done" || state.status === "needs_user", text: lastText, tokens: state.tokensUsed };
104484
+ const tokens = Math.max(0, state.tokensUsed - cur.tokensBase);
104485
+ cur.tokensBase = state.tokensUsed;
104486
+ return { success: (state.status === "done" || state.status === "needs_user") && patchFailures === 0, text: lastText, tokens };
104481
104487
  }
104482
- var TERMINAL, DEFAULT_BUDGET, DEFAULT_MAX_WAIT_MS, CoreTurnError;
104488
+ var newRunCursor, TERMINAL, DEFAULT_BUDGET, DEFAULT_MAX_WAIT_MS, CoreTurnError;
104483
104489
  var init_core_turn = __esm({
104484
104490
  "src/core/core-turn.ts"() {
104485
104491
  "use strict";
104492
+ newRunCursor = () => ({ after: 0, known: {}, patches: {}, applied: [], tokensBase: 0 });
104486
104493
  TERMINAL = /* @__PURE__ */ new Set(["done", "stopped", "core_failed", "needs_user"]);
104487
104494
  DEFAULT_BUDGET = 5e5;
104488
104495
  DEFAULT_MAX_WAIT_MS = 30 * 6e4;
@@ -104514,11 +104521,13 @@ async function detectRepo(cwd2, env3) {
104514
104521
  }
104515
104522
  function patchApplier(executor, cwd2, runId) {
104516
104523
  return async ({ taskId, patch }) => {
104517
- const dir = join14(cwd2, ".git", "spacecode-core", runId);
104524
+ const top = await gitOut(cwd2, ["rev-parse", "--show-toplevel"]) ?? cwd2;
104525
+ const gitDir = await gitOut(cwd2, ["rev-parse", "--absolute-git-dir"]) ?? join14(top, ".git");
104526
+ const dir = join14(gitDir, "spacecode-core", runId.replace(/[^A-Za-z0-9_.-]/g, "_"));
104518
104527
  await mkdir6(dir, { recursive: true });
104519
104528
  const file = join14(dir, `${taskId.replace(/[^A-Za-z0-9_.-]/g, "_")}.patch`);
104520
104529
  await writeFile6(file, patch, { encoding: "utf-8", mode: 384 });
104521
- const res = await executor.execute("bash", { command: `git apply --3way ${JSON.stringify(file)}` });
104530
+ const res = await executor.execute("bash", { command: `git -C ${sq(top)} apply --3way ${sq(file)}` });
104522
104531
  return res.is_error ? { applied: false, detail: `${res.output.slice(0, 200).trim() || "git apply failed"}; patch kept at ${file}` } : { applied: true, detail: "git apply --3way" };
104523
104532
  };
104524
104533
  }
@@ -104537,6 +104546,7 @@ async function runCoreForRepl(o) {
104537
104546
  o.runIdRef.current = id;
104538
104547
  runIdForApply = id;
104539
104548
  },
104549
+ cursor: o.cursor,
104540
104550
  newId: () => crypto.randomUUID(),
104541
104551
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
104542
104552
  now: Date.now,
@@ -104549,13 +104559,14 @@ async function runCoreForRepl(o) {
104549
104559
  const messages = [...o.history, { role: "user", content: o.prompt }, { role: "assistant", content: [{ type: "text", text: turn.text || "(no reply)" }] }];
104550
104560
  return { success: turn.success, messages, totalInputTokens: turn.tokens, totalOutputTokens: 0 };
104551
104561
  }
104552
- var coreModeOn;
104562
+ var coreModeOn, sq;
104553
104563
  var init_core_mode = __esm({
104554
104564
  "src/core/core-mode.ts"() {
104555
104565
  "use strict";
104556
104566
  init_core_turn();
104557
104567
  init_profile_runtime();
104558
104568
  coreModeOn = (env3) => env3.SPACECODE_CORE?.trim().toLowerCase() === "on";
104569
+ sq = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
104559
104570
  }
104560
104571
  });
104561
104572
 
@@ -104594,6 +104605,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
104594
104605
  }, [projectRoot]);
104595
104606
  const sessionIdRef = (0, import_react30.useRef)(null);
104596
104607
  const coreRunIdRef = (0, import_react30.useRef)(null);
104608
+ const coreCursorRef = (0, import_react30.useRef)(newRunCursor());
104597
104609
  const sessionCreatedAtRef = (0, import_react30.useRef)(null);
104598
104610
  const sessionTokensRef = (0, import_react30.useRef)({ input: 0, output: 0 });
104599
104611
  const profilePromiseRef = (0, import_react30.useRef)(null);
@@ -104841,6 +104853,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
104841
104853
  sessionIdRef.current = null;
104842
104854
  sessionCreatedAtRef.current = null;
104843
104855
  coreRunIdRef.current = null;
104856
+ coreCursorRef.current = newRunCursor();
104844
104857
  return;
104845
104858
  case "help":
104846
104859
  notice(formatHelp());
@@ -105152,6 +105165,7 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
105152
105165
  sessionId: sessionIdRef.current ??= sessions ? sessions.newId() : crypto.randomUUID(),
105153
105166
  cwd: projectRoot,
105154
105167
  runIdRef: coreRunIdRef,
105168
+ cursor: coreCursorRef.current,
105155
105169
  signal: controller.signal,
105156
105170
  emit,
105157
105171
  executor: toolset.executor,
@@ -105270,6 +105284,7 @@ var init_useReplController = __esm({
105270
105284
  init_src();
105271
105285
  init_command_loader();
105272
105286
  init_core_mode();
105287
+ init_core_turn();
105273
105288
  init_slash_commands();
105274
105289
  init_useEventAccumulator();
105275
105290
  gitTopLevel = () => new Promise((resolve2) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spacecode",
3
- "version": "2.11.0",
3
+ "version": "2.11.1",
4
4
  "description": "SpaceCode - launch cloud dev environments with AI coding agents",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",