@kevin5251984/guild 0.2.18 → 0.2.20

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/src/generate.ts CHANGED
@@ -350,16 +350,16 @@ When work belongs to someone else, put @handle at the start of a line with a wri
350
350
  - Done when
351
351
  - Constraints / out of scope
352
352
  - Files or evidence
353
- Each line-start @handle on this quest starts that seat. Mentions in the middle of a sentence do not dispatch.
354
- Do not @all unless the human did. Do not recruit extra people; the human staffs the roster (max ${CHANNEL_ROSTER_CAP} on a quest).
355
- Only line-start @handle a seat the human already named this turn, or that they asked you to split the work to. Do not invent a third seat. If B must wait for A, do not @ B this turn.
353
+ Each line-start @handle of a bot already on this quest starts that seat this turn. A markdown numbered list item that leads with a teammate (1. @design) also starts them, even if the handle is wrapped in backticks. Mentions that are only commentary in a sentence do not dispatch.
354
+ Do not @all unless the human did. Do not recruit extra people; the human staffs the roster with 加入 (max ${CHANNEL_ROSTER_CAP} on a quest). @handle never adds a seat.
355
+ You may @handle any staffed teammate whose job is the next step, even if the human only named you this turn. That is how the hall continues. Do not @handle a bot who is not on this quest. Do not dump the same work on every seat. If two seats must run in order, only @ the seat that can start now. Later seats stay in prose (四席完成後由 @infra, 通過後 @marketing, 最後 @infra) — those do not start this turn. After the first wave reports, @handle the next seat with a spec. Do not write a plan and stop.
356
356
  Stay quiet: no status theater, no "I'll start now." Speak when you finish, block, or need a decision. Money, sends, and destructive actions wait for the human.
357
357
 
358
358
  Harness this turn (Memory → Plan → Skills → Act):
359
359
  - Memory: Channel.md is the task. MEMORY.md is standing notes. The compact log is working memory — do not recap the whole thread.
360
360
  - Plan: one local directive (goal + done when) before tools. Revise it when evidence changes.
361
361
  - Skills: the catalog is availability, not a todo. Call \`skill\` only when this directive matches. Do not load every skill.
362
- - Act: inspect, smallest change, verify, stop. Spawn is a specialist for a bounded slice of THIS seat's job (explore / review / implement) with a fresh context. Do not spawn to do another staffed bot's job — @handle them instead.`;
362
+ - Act: you coordinate this seat. Spawn first when the work is a repo survey (\`explorer\` / luna-explore), a critique (\`reviewer\`), or a bounded isolated patch (\`worker\` / luna-general); then verify the child's evidence and decide. Independent surveys: spawn background=true, keep working, then read_spawn before you answer. Sequential: background=false and wait. Do not spawn for one known file, a one-line change, or a question that needs no repo. Do not let children commit, push, or make the architecture call. Do not skip spawn just because you can do the work yourself. Do not spawn to do another staffed bot's job — @handle them instead.`;
363
363
 
364
364
  export function buildChatSystem(input: {
365
365
  botName: string;
@@ -397,29 +397,57 @@ export function buildChatSystem(input: {
397
397
  "</system-reminder>",
398
398
  ].join("\n")
399
399
  : "";
400
- const spawnLine = subagents.length
401
- ? [
402
- "<available_subagents>",
403
- ...subagents.slice(0, 40).map((item) => {
404
- const key = item.slug || item.name;
405
- const desc = (item.description || item.name)
406
- .replace(/\s+/g, " ")
407
- .trim()
408
- .slice(0, 220);
409
- const mode = item.readOnly ? "read-only" : "read-write";
410
- return `- \`${key}\` (${mode}): ${desc}`;
411
- }),
412
- "</available_subagents>",
413
- "Call spawn with the exact name (or slug) and a self-contained prompt. If the user writes /name matching a subagent, spawn that one. The child has a fresh context and returns a summary.",
414
- wantSpawn.length
415
- ? `This turn the user invoked ${wantSpawn
416
- .map((item) => "`/" + (item.slug || item.name) + "`")
417
- .join(", ")}. Call spawn with that exact name first, with a self-contained prompt covering their request. Do not skip this and do the work yourself.`
418
- : "",
419
- ]
420
- .filter(Boolean)
421
- .join("\n")
422
- : "";
400
+ const spawnCatalog: Array<{
401
+ slug?: string;
402
+ name: string;
403
+ description?: string;
404
+ readOnly?: boolean;
405
+ }> = subagents.length
406
+ ? subagents
407
+ : [
408
+ {
409
+ slug: "explorer",
410
+ name: "explorer",
411
+ description:
412
+ "Read-only codebase search. Returns absolute paths and a direct answer.",
413
+ readOnly: true,
414
+ },
415
+ {
416
+ slug: "reviewer",
417
+ name: "reviewer",
418
+ description: "Read-only review of correctness, risk, and missing tests.",
419
+ readOnly: true,
420
+ },
421
+ {
422
+ slug: "worker",
423
+ name: "worker",
424
+ description:
425
+ "Implementation executor. Smallest correct change, then verify.",
426
+ readOnly: false,
427
+ },
428
+ ];
429
+ const spawnLine = [
430
+ "<available_subagents>",
431
+ ...spawnCatalog.slice(0, 40).map((item) => {
432
+ const key = item.slug || item.name;
433
+ const desc = (item.description || item.name)
434
+ .replace(/\s+/g, " ")
435
+ .trim()
436
+ .slice(0, 220);
437
+ const mode = item.readOnly ? "read-only" : "read-write";
438
+ return `- \`${key}\` (${mode}): ${desc}`;
439
+ }),
440
+ "</available_subagents>",
441
+ "Call spawn with the exact name (or slug) and a self-contained prompt (Pi: agent+task). Default: explorer to orient across unknown files, reviewer to critique a change, worker for an isolated patch. Independent slices: several spawn calls in this round, or tasks: [{name, prompt}]. You stay this seat's coordinator. Skipping spawn and reading the whole tree yourself is the wrong default.",
442
+ "If the user writes /name matching a subagent, spawn that one. The child has a fresh context and returns a summary.",
443
+ wantSpawn.length
444
+ ? `This turn the user invoked ${wantSpawn
445
+ .map((item) => "`/" + (item.slug || item.name) + "`")
446
+ .join(", ")}. Call spawn with that exact name first, with a self-contained prompt covering their request. Do not skip this and do the work yourself.`
447
+ : "",
448
+ ]
449
+ .filter(Boolean)
450
+ .join("\n");
423
451
  const channel = (input.channelMd ?? "").trim();
424
452
  const channelBlock = channel
425
453
  ? `# Channel.md\nThis channel's operating notes written by the user. Follow them for this room. They outrank MEMORY.md.\n\n${channel.slice(0, 4000)}`
@@ -515,7 +543,7 @@ export function localChatReply(
515
543
  userMessage: string,
516
544
  ): string {
517
545
  const clip = userMessage.trim().slice(0, 120);
518
- return `【${botName} @${handle}】收到。「${clip}」\n\n沒有可用模型,本機工具還沒辦法跑。到私訊幫我選一個模型後再問。`;
546
+ return `【${botName} @${handle}】收到。「${clip}」\n\n沒有可用模型,本機工具還沒辦法跑。到模型頁(/settings)連接訂閱或填 API key,套用主模型後再問。`;
519
547
  }
520
548
 
521
549
  async function tryChatLlm(
@@ -569,6 +597,8 @@ async function tryChatLlm(
569
597
  env,
570
598
  prefer,
571
599
  checkpoint: input.compact,
600
+ onProgress: input.onProgress,
601
+ signal: input.signal,
572
602
  });
573
603
  if (packed.compacted && packed.checkpoint && input.onCompact) {
574
604
  input.onCompact(packed.checkpoint);
@@ -594,6 +624,7 @@ async function tryChatLlm(
594
624
  pullSteers: input.pullSteers,
595
625
  signal: input.signal,
596
626
  mcpTools: input.mcpTools ?? (await listMcpToolRefs(dataDir)),
627
+ spawnHandles: new Map(),
597
628
  ...policyFor(env, {
598
629
  sandbox: input.sandbox,
599
630
  workspace: input.workspace,