@selesai/code 0.3.8 → 0.3.9

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: builder
3
- model: tokenin/qwen3.5-397b
3
+ model: tokenin/qwen3.6-35b
4
4
  thinking: high
5
5
  description: Implementation agent for normal tasks handoffs
6
6
  systemPromptMode: replace
@@ -175,6 +175,8 @@ interface WorkflowController {
175
175
  // Ceiling: not persisted across session restart — a restart re-runs the
176
176
  // loop from round 0. Acceptable for a prototype workflow.
177
177
  loopState?: LoopState;
178
+ seenToolCallIds: Set<string>;
179
+ lastBlockedKey?: string;
178
180
  }
179
181
 
180
182
  // ponytail: Plan 3 — loop orchestration state. The parent model does not
@@ -338,6 +340,30 @@ function applyEffect(
338
340
  }
339
341
  }
340
342
 
343
+ function blockedKeyFor(eff: WorkflowEffect): string | undefined {
344
+ if (eff.kind !== "blocked") return undefined;
345
+ return `${eff.phase}:${eff.missing}:${eff.reason ?? ""}`;
346
+ }
347
+
348
+ function applyControllerEffect(
349
+ controller: WorkflowController,
350
+ ctx: ExtensionContext,
351
+ eff: WorkflowEffect,
352
+ ): void {
353
+ const blockedKey = blockedKeyFor(eff);
354
+ if (blockedKey) {
355
+ if (controller.lastBlockedKey === blockedKey) return;
356
+ controller.lastBlockedKey = blockedKey;
357
+ } else if (eff.kind !== "noOp") {
358
+ controller.lastBlockedKey = undefined;
359
+ }
360
+ applyEffect(controller.pi, ctx, controller.config, eff);
361
+ }
362
+
363
+ function isSubagentManagementAction(input: Record<string, unknown> | undefined): boolean {
364
+ return typeof input?.action === "string";
365
+ }
366
+
341
367
  function registerSharedArtifactWriter(pi: ExtensionAPI): void {
342
368
  if (registry.writerRegisteredFor.has(pi)) return;
343
369
  registry.writerRegisteredFor.add(pi);
@@ -374,7 +400,7 @@ function registerSharedArtifactWriter(pi: ExtensionAPI): void {
374
400
  await mkdir(snap.artifactDir, { recursive: true });
375
401
  await writeFile(path, params.content, "utf8");
376
402
  const eff = await controller.sm.onArtifactMaybe(controller.deps);
377
- applyEffect(pi, ctx, controller.config, eff);
403
+ applyControllerEffect(controller, ctx, eff);
378
404
  if (eff.kind === "blocked" && eff.reason) {
379
405
  return {
380
406
  content: [{ type: "text", text: `Wrote ${path}, but it is not approved: ${eff.reason}. Re-write it via write_workflow_artifact to add the required marker.` }],
@@ -410,7 +436,7 @@ export function createWorkflowExtension(
410
436
  },
411
437
  ];
412
438
  const sm = new WorkflowStateMachine({ ...config, skipRules });
413
- const controller: WorkflowController = { pi, config, sm, deps };
439
+ const controller: WorkflowController = { pi, config, sm, deps, seenToolCallIds: new Set() };
414
440
  registry.controllers.push(controller);
415
441
  registerSharedArtifactWriter(pi);
416
442
  const { start, end } = options.toolNames;
@@ -440,7 +466,11 @@ export function createWorkflowExtension(
440
466
  };
441
467
  }
442
468
  const eff = await sm.start(params.goal, deps);
443
- applyEffect(pi, ctx, config, eff);
469
+ if (eff.kind === "started") {
470
+ controller.seenToolCallIds.clear();
471
+ controller.lastBlockedKey = undefined;
472
+ }
473
+ applyControllerEffect(controller, ctx, eff);
444
474
  if (eff.kind === "alreadyActive") {
445
475
  return {
446
476
  content: [
@@ -480,7 +510,11 @@ export function createWorkflowExtension(
480
510
  parameters: Type.Object({}),
481
511
  async execute(_id, _params, _signal, _onUpdate, ctx) {
482
512
  const eff = await sm.end(deps);
483
- applyEffect(pi, ctx, config, eff);
513
+ applyControllerEffect(controller, ctx, eff);
514
+ if (eff.kind === "closed") {
515
+ controller.seenToolCallIds.clear();
516
+ controller.lastBlockedKey = undefined;
517
+ }
484
518
  switch (eff.kind) {
485
519
  case "closed":
486
520
  return {
@@ -575,7 +609,7 @@ export function createWorkflowExtension(
575
609
  if (tool !== "subagent" && tool !== "write" && tool !== "edit") return;
576
610
  const snap = sm.snapshot;
577
611
  if (!snap.active) return;
578
- if (tool === "subagent" && typeof event.input?.action === "string") return;
612
+ if (tool === "subagent" && isSubagentManagementAction(event.input)) return;
579
613
  const file = config.phaseArtifacts[snap.phase];
580
614
  if (tool === "write" || tool === "edit") {
581
615
  return {
@@ -615,13 +649,18 @@ export function createWorkflowExtension(
615
649
  // ── tool_result: auto-advance after subagent/bash artifacts. ──
616
650
  pi.on("tool_result", async (event: any, ctx: ExtensionContext) => {
617
651
  if (event.toolName !== "bash" && event.toolName !== "subagent") return;
652
+ if (sm.snapshot.active && typeof event.toolCallId === "string") {
653
+ if (controller.seenToolCallIds.has(event.toolCallId)) return;
654
+ controller.seenToolCallIds.add(event.toolCallId);
655
+ }
618
656
  // ponytail: a failed phase-owner tool used to leave the workflow quiet
619
657
  // until the user manually typed /prototype or /quick continue. Re-check
620
658
  // first (in case the tool wrote the artifact before failing), then if
621
659
  // nothing advanced, re-queue the current phase prompt automatically.
622
660
  if (event.isError && sm.snapshot.active) {
661
+ if (event.toolName === "subagent" && isSubagentManagementAction(event.input)) return;
623
662
  const eff = await sm.onArtifactMaybe(deps);
624
- applyEffect(pi, ctx, config, eff);
663
+ applyControllerEffect(controller, ctx, eff);
625
664
  if (eff.kind === "noOp") {
626
665
  const retry = sm.continueCurrent();
627
666
  continueAgent(
@@ -648,12 +687,15 @@ export function createWorkflowExtension(
648
687
  // on agent-listing output (the `subagent list` result is plain
649
688
  // text). Execution calls set `agent`/`chain`/`tasks`; management
650
689
  // calls set `action`. Only fall back for execution calls.
651
- if (typeof event.input?.action !== "string") {
690
+ if (!isSubagentManagementAction(event.input)) {
652
691
  const expectedPath = resolve(sm.snapshot.artifactDir, file);
653
692
  const text = textFromToolResultContent(event.content);
654
693
  if (text) {
655
- let shouldWrite = !(await realFileExists(expectedPath));
656
- if (!shouldWrite) {
694
+ const validator = validatorForPhaseArtifact(config, phase);
695
+ let shouldWrite = false;
696
+ if (!(await realFileExists(expectedPath))) {
697
+ shouldWrite = !validator || validator(text).ok;
698
+ } else {
657
699
  try {
658
700
  const current = await readFile(expectedPath, "utf8");
659
701
  shouldWrite = shouldReplaceInvalidArtifact(config, phase, current, text);
@@ -726,7 +768,7 @@ or
726
768
  // onArtifactMaybe (a no-op unless loop-complete.md exists).
727
769
  }
728
770
  const eff = await sm.onArtifactMaybe(deps);
729
- applyEffect(pi, ctx, config, eff);
771
+ applyControllerEffect(controller, ctx, eff);
730
772
  // Clear loop state once we have left the loop phase (advanced to audit).
731
773
  if (sm.snapshot.phase !== "loop") {
732
774
  controller.loopState = undefined;
@@ -759,7 +801,7 @@ or
759
801
  if (choice === undefined) return;
760
802
  if (choice.startsWith("Continue")) {
761
803
  const eff = sm.continueCurrent();
762
- applyEffect(pi, ctx, config, eff);
804
+ applyControllerEffect(controller, ctx, eff);
763
805
  if (eff.kind === "advanced" && eff.prompt) {
764
806
  // continueCurrent already applied footer+entry; send the prompt.
765
807
  continueAgent(pi, ctx, eff.prompt);
@@ -796,7 +838,11 @@ or
796
838
  return;
797
839
  }
798
840
  const eff = await sm.start(args, deps);
799
- applyEffect(pi, ctx, config, eff);
841
+ if (eff.kind === "started") {
842
+ controller.seenToolCallIds.clear();
843
+ controller.lastBlockedKey = undefined;
844
+ }
845
+ applyControllerEffect(controller, ctx, eff);
800
846
  },
801
847
  });
802
848
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@selesai/code",
3
- "version": "0.3.8",
3
+ "version": "0.3.9",
4
4
  "description": "Selesai coding agent",
5
5
  "type": "module",
6
6
  "piConfig": {