spacecode 2.11.0 → 2.12.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/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}`, {
@@ -104424,7 +104426,7 @@ async function runCoreTurn(d, prompt) {
104424
104426
  const created = await call("POST", "", {
104425
104427
  sessionId: d.sessionId,
104426
104428
  budgetTokens: d.budgetTokens ?? DEFAULT_BUDGET,
104427
- ...d.repo ? { repoUrl: d.repo.url, ...d.repo.branch ? { branch: d.repo.branch } : {} } : {}
104429
+ ...d.repo ? { repoUrl: d.repo.url, ...d.repo.branch ? { branch: d.repo.branch } : {}, ...d.ghToken ? { ghToken: d.ghToken } : {} } : {}
104428
104430
  });
104429
104431
  runId = created.runId;
104430
104432
  d.setRunId(runId);
@@ -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;
@@ -104512,13 +104519,20 @@ async function detectRepo(cwd2, env3) {
104512
104519
  const branch = env3.SPACECODE_CORE_BRANCH?.trim();
104513
104520
  return { url, ...branch ? { branch } : {} };
104514
104521
  }
104522
+ function shareableGithubToken(env3) {
104523
+ if (env3.SPACECODE_CORE_SHARE_GITHUB_TOKEN?.trim().toLowerCase() !== "on") return void 0;
104524
+ const t = (env3.GH_TOKEN ?? env3.GITHUB_TOKEN)?.trim();
104525
+ return t ? t : void 0;
104526
+ }
104515
104527
  function patchApplier(executor, cwd2, runId) {
104516
104528
  return async ({ taskId, patch }) => {
104517
- const dir = join14(cwd2, ".git", "spacecode-core", runId);
104529
+ const top = await gitOut(cwd2, ["rev-parse", "--show-toplevel"]) ?? cwd2;
104530
+ const gitDir = await gitOut(cwd2, ["rev-parse", "--absolute-git-dir"]) ?? join14(top, ".git");
104531
+ const dir = join14(gitDir, "spacecode-core", runId.replace(/[^A-Za-z0-9_.-]/g, "_"));
104518
104532
  await mkdir6(dir, { recursive: true });
104519
104533
  const file = join14(dir, `${taskId.replace(/[^A-Za-z0-9_.-]/g, "_")}.patch`);
104520
104534
  await writeFile6(file, patch, { encoding: "utf-8", mode: 384 });
104521
- const res = await executor.execute("bash", { command: `git apply --3way ${JSON.stringify(file)}` });
104535
+ const res = await executor.execute("bash", { command: `git -C ${sq(top)} apply --3way ${sq(file)}` });
104522
104536
  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
104537
  };
104524
104538
  }
@@ -104532,11 +104546,13 @@ async function runCoreForRepl(o) {
104532
104546
  apiKey: o.apiKey,
104533
104547
  sessionId: o.sessionId,
104534
104548
  repo,
104549
+ ...shareableGithubToken(o.env) ? { ghToken: shareableGithubToken(o.env) } : {},
104535
104550
  getRunId: () => o.runIdRef.current,
104536
104551
  setRunId: (id) => {
104537
104552
  o.runIdRef.current = id;
104538
104553
  runIdForApply = id;
104539
104554
  },
104555
+ cursor: o.cursor,
104540
104556
  newId: () => crypto.randomUUID(),
104541
104557
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
104542
104558
  now: Date.now,
@@ -104549,13 +104565,14 @@ async function runCoreForRepl(o) {
104549
104565
  const messages = [...o.history, { role: "user", content: o.prompt }, { role: "assistant", content: [{ type: "text", text: turn.text || "(no reply)" }] }];
104550
104566
  return { success: turn.success, messages, totalInputTokens: turn.tokens, totalOutputTokens: 0 };
104551
104567
  }
104552
- var coreModeOn;
104568
+ var coreModeOn, sq;
104553
104569
  var init_core_mode = __esm({
104554
104570
  "src/core/core-mode.ts"() {
104555
104571
  "use strict";
104556
104572
  init_core_turn();
104557
104573
  init_profile_runtime();
104558
104574
  coreModeOn = (env3) => env3.SPACECODE_CORE?.trim().toLowerCase() === "on";
104575
+ sq = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
104559
104576
  }
104560
104577
  });
104561
104578
 
@@ -104594,6 +104611,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
104594
104611
  }, [projectRoot]);
104595
104612
  const sessionIdRef = (0, import_react30.useRef)(null);
104596
104613
  const coreRunIdRef = (0, import_react30.useRef)(null);
104614
+ const coreCursorRef = (0, import_react30.useRef)(newRunCursor());
104597
104615
  const sessionCreatedAtRef = (0, import_react30.useRef)(null);
104598
104616
  const sessionTokensRef = (0, import_react30.useRef)({ input: 0, output: 0 });
104599
104617
  const profilePromiseRef = (0, import_react30.useRef)(null);
@@ -104841,6 +104859,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
104841
104859
  sessionIdRef.current = null;
104842
104860
  sessionCreatedAtRef.current = null;
104843
104861
  coreRunIdRef.current = null;
104862
+ coreCursorRef.current = newRunCursor();
104844
104863
  return;
104845
104864
  case "help":
104846
104865
  notice(formatHelp());
@@ -105152,6 +105171,7 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
105152
105171
  sessionId: sessionIdRef.current ??= sessions ? sessions.newId() : crypto.randomUUID(),
105153
105172
  cwd: projectRoot,
105154
105173
  runIdRef: coreRunIdRef,
105174
+ cursor: coreCursorRef.current,
105155
105175
  signal: controller.signal,
105156
105176
  emit,
105157
105177
  executor: toolset.executor,
@@ -105270,6 +105290,7 @@ var init_useReplController = __esm({
105270
105290
  init_src();
105271
105291
  init_command_loader();
105272
105292
  init_core_mode();
105293
+ init_core_turn();
105273
105294
  init_slash_commands();
105274
105295
  init_useEventAccumulator();
105275
105296
  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.12.0",
4
4
  "description": "SpaceCode - launch cloud dev environments with AI coding agents",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",