bazilion 0.11.0 → 0.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.
Files changed (69) hide show
  1. package/dist/cli.js +433 -146
  2. package/dist/cli.js.map +1 -1
  3. package/dist/daemon.js +1382 -232
  4. package/dist/daemon.js.map +1 -1
  5. package/dist/migrations/0001_init.sql +81 -0
  6. package/dist/web/client/assets/AgentTabs-R6gSjIj3.js +1 -0
  7. package/dist/web/client/assets/{ProductionTeamPolicyEditor-B6dbpuYy.js → ProductionTeamPolicyEditor-CSL3W7XM.js} +2 -2
  8. package/dist/web/client/assets/{ProfileCommunicationEditor-Bf8UvmZT.js → ProfileCommunicationEditor-CwU0QkmE.js} +1 -1
  9. package/dist/web/client/assets/{TeamTabs-sQBHChtd.js → TeamTabs-C1Hakkff.js} +1 -1
  10. package/dist/web/client/assets/{TemplatesTabs-DPsuwsWB.js → TemplatesTabs-klEydJ8-.js} +1 -1
  11. package/dist/web/client/assets/{_id-C9rkCclz.js → _id-BOC7ioJu.js} +1 -1
  12. package/dist/web/client/assets/{_id-DYASsGQ8.js → _id-CnVq-rED.js} +1 -1
  13. package/dist/web/client/assets/{_id-DE4IY5bL.js → _id-e4u7dmBb.js} +1 -1
  14. package/dist/web/client/assets/{_id-D65IzC8H.js → _id-wkjjWLz0.js} +1 -1
  15. package/dist/web/client/assets/{activity-C7o6-xHg.js → activity-uqhwzmiy.js} +1 -1
  16. package/dist/web/client/assets/{agents-UroO4d25.js → agents-B2usE-L_.js} +1 -1
  17. package/dist/web/client/assets/{agents-h8yWXQ2H.js → agents-FaQtkAWs.js} +1 -1
  18. package/dist/web/client/assets/{approvals-DKhF0YLC.js → approvals-DqLE6ttw.js} +1 -1
  19. package/dist/web/client/assets/attention-CkGozYnJ.js +1 -0
  20. package/dist/web/client/assets/attention-Gavt2yEp.js +1 -0
  21. package/dist/web/client/assets/chevron-down-CJk0zGNT.js +1 -0
  22. package/dist/web/client/assets/{config-q8JDresK.js → config-xrXoDEWM.js} +1 -1
  23. package/dist/web/client/assets/{context-CBucAoi6.js → context-ZWqx3uiC.js} +1 -1
  24. package/dist/web/client/assets/inbox-BoIvl9ZF.js +1 -0
  25. package/dist/web/client/assets/{index-DUXAevpZ.js → index-nb2MV8X1.js} +3 -3
  26. package/dist/web/client/assets/learning-6_oYn1l7.js +1 -0
  27. package/dist/web/client/assets/{login-CNAV9Wer.js → login-DM1ytxQL.js} +1 -1
  28. package/dist/web/client/assets/{mcp-BKdEFwHO.js → mcp-CetZbyUh.js} +1 -1
  29. package/dist/web/client/assets/{members-BDqZQh26.js → members-DdqWtwZd.js} +1 -1
  30. package/dist/web/client/assets/{memory-DVdEVr6L.js → memory-FNrxFHmC.js} +1 -1
  31. package/dist/web/client/assets/plus-C3GroITC.js +1 -0
  32. package/dist/web/client/assets/{policy-DdDctqvg.js → policy-BKPejYRI.js} +1 -1
  33. package/dist/web/client/assets/{routes-BEfALSZv.js → routes-CH1ZTTo-.js} +1 -1
  34. package/dist/web/client/assets/{services-DZP2ybzb.js → services-zk9SqN06.js} +1 -1
  35. package/dist/web/client/assets/{skills-DoeIjITs.js → skills-tXDpomHW.js} +1 -1
  36. package/dist/web/client/assets/styles-DMltY3su.css +2 -0
  37. package/dist/web/client/assets/{teams-W3j-UoJ9.js → teams-BZYjekBN.js} +1 -1
  38. package/dist/web/client/assets/{teams-DlHRnrQw.js → teams-D1c8zlxs.js} +1 -1
  39. package/dist/web/client/assets/{telegram-2OdeGv4E.js → telegram-DCe1CWMt.js} +1 -1
  40. package/dist/web/client/assets/{tokens-MuBfmgue.js → tokens-DAPjRS0N.js} +1 -1
  41. package/dist/web/client/assets/triggers-D8R-5guA.js +1 -0
  42. package/dist/web/client/assets/{welcome-CvNr5IMi.js → welcome-CuK0shDJ.js} +1 -1
  43. package/dist/web/server/assets/{AgentTabs-DoMDJBXH.js → AgentTabs-aXdsiiMp.js} +5 -0
  44. package/dist/web/server/assets/{_id-Cg7x6q3g.js → _id-BHYLL8M-.js} +2 -2
  45. package/dist/web/server/assets/{_id-DqON-59C.js → _id-FcHdSocl.js} +1 -1
  46. package/dist/web/server/assets/{_tanstack-start-manifest_v-BXauUXg6.js → _tanstack-start-manifest_v-fLsYERQ-.js} +76 -53
  47. package/dist/web/server/assets/attention-BW_6ddkj.js +11 -0
  48. package/dist/web/server/assets/attention-BxkTf337.js +14 -0
  49. package/dist/web/server/assets/attention-CM-Ks1N9.js +19 -0
  50. package/dist/web/server/assets/attention-w21GQIGH.js +264 -0
  51. package/dist/web/server/assets/{inbox-CUIyf9MT.js → inbox-BLUzA5sQ.js} +40 -3
  52. package/dist/web/server/assets/{inbox-B3-TRsNI.js → inbox-CzlKT9Wg.js} +5 -3
  53. package/dist/web/server/assets/{inbox-BardsGyy.js → inbox-ZvDNAjaM.js} +1 -1
  54. package/dist/web/server/assets/learning-8zav4pIT.js +33 -0
  55. package/dist/web/server/assets/learning-B7PBkvER.js +16 -0
  56. package/dist/web/server/assets/learning-Cq7K2heb.js +312 -0
  57. package/dist/web/server/assets/{router-C5_NjTsP.js → router-CQuj7553.js} +80 -47
  58. package/dist/web/server/assets/{triggers-CBl4yMyw.js → triggers-BZPl5CHs.js} +1 -1
  59. package/dist/web/server/assets/{triggers-_3rdhwSN.js → triggers-CSNNzRIi.js} +2 -2
  60. package/dist/web/server/server.js +11 -3
  61. package/dist/worker.js +166 -36
  62. package/dist/worker.js.map +1 -1
  63. package/package.json +3 -3
  64. package/dist/web/client/assets/AgentTabs-B3hq5a3e.js +0 -1
  65. package/dist/web/client/assets/chevron-down-BK6NPMMP.js +0 -1
  66. package/dist/web/client/assets/inbox-BwoRikj1.js +0 -1
  67. package/dist/web/client/assets/plus-b4yFXP0O.js +0 -1
  68. package/dist/web/client/assets/styles-BYM0UGdF.css +0 -2
  69. package/dist/web/client/assets/triggers-CX7RMQFQ.js +0 -1
package/dist/worker.js CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // ../daemon/src/runtime/worker/entry.ts
4
- import { randomUUID as randomUUID17 } from "crypto";
4
+ import { randomUUID as randomUUID19 } from "crypto";
5
+ import { mkdtempSync as mkdtempSync3, rmSync as rmSync12 } from "fs";
5
6
  import { join as join19 } from "path";
7
+ import { Type as Type3 } from "typebox";
6
8
 
7
9
  // ../daemon/src/core/agent/delete.ts
8
10
  import { randomUUID } from "crypto";
@@ -18,8 +20,11 @@ import { join as join2 } from "path";
18
20
  // ../daemon/src/core/profile/identity.ts
19
21
  import { readFileSync as readFileSync2 } from "fs";
20
22
 
21
- // ../daemon/src/core/agent/spawn.ts
23
+ // ../daemon/src/core/repos/agentLessonProposals.ts
22
24
  import { randomUUID as randomUUID2 } from "crypto";
25
+
26
+ // ../daemon/src/core/agent/spawn.ts
27
+ import { randomUUID as randomUUID3 } from "crypto";
23
28
  import { mkdirSync as mkdirSync3, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
24
29
  import { join as join7 } from "path";
25
30
 
@@ -85,14 +90,17 @@ function resolvePaths(home) {
85
90
  import { existsSync as existsSync6, rmSync as rmSync4 } from "fs";
86
91
 
87
92
  // ../daemon/src/core/repos/teamTemplates.ts
88
- import { randomUUID as randomUUID3 } from "crypto";
93
+ import { randomUUID as randomUUID4 } from "crypto";
89
94
 
90
95
  // ../daemon/src/core/profile/update.ts
91
96
  import { writeFileSync as writeFileSync5 } from "fs";
92
97
  import { join as join10 } from "path";
93
98
 
99
+ // ../daemon/src/core/repos/agentReviews.ts
100
+ import { randomUUID as randomUUID5 } from "crypto";
101
+
94
102
  // ../daemon/src/core/repos/communicationApprovals.ts
95
- import { createHash, randomUUID as randomUUID4 } from "crypto";
103
+ import { createHash, randomUUID as randomUUID6 } from "crypto";
96
104
  var DEFAULT_TTL_MS = 24 * 60 * 60 * 1e3;
97
105
 
98
106
  // ../daemon/src/core/services.ts
@@ -529,6 +537,13 @@ var SERVICES = [
529
537
  label: "Extra environment allowlist",
530
538
  placeholder: "MY_SAFE_VARIABLE,ANOTHER_SAFE_VARIABLE",
531
539
  description: "Optional comma-separated variable names copied into the container. Provider credentials remain excluded unless explicitly named."
540
+ },
541
+ {
542
+ envVar: "BAZILION_AGENT_LOOP_MAX_HOPS",
543
+ kind: "config",
544
+ label: "Agent-message hop limit",
545
+ placeholder: "8",
546
+ description: "Maximum causal hops in an agent-to-agent message chain before the daemon stops it. Default 8."
532
547
  }
533
548
  ]
534
549
  },
@@ -634,22 +649,22 @@ var CONFIG_KEYS = [
634
649
  var CONFIG_KEY_SET = new Set(CONFIG_KEYS);
635
650
 
636
651
  // ../daemon/src/core/repos/mcpServers.ts
637
- import { randomUUID as randomUUID5 } from "crypto";
652
+ import { randomUUID as randomUUID7 } from "crypto";
638
653
 
639
654
  // ../daemon/src/core/repos/messages.ts
640
- import { randomUUID as randomUUID6 } from "crypto";
655
+ import { randomUUID as randomUUID8 } from "crypto";
641
656
 
642
657
  // ../daemon/src/core/repos/secrets.ts
643
658
  import { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from "crypto";
644
659
 
645
660
  // ../daemon/src/core/repos/triggerDispatches.ts
646
- import { randomUUID as randomUUID7 } from "crypto";
661
+ import { randomUUID as randomUUID9 } from "crypto";
647
662
 
648
663
  // ../daemon/src/core/repos/triggers.ts
649
- import { randomUUID as randomUUID8 } from "crypto";
664
+ import { randomUUID as randomUUID10 } from "crypto";
650
665
 
651
666
  // ../daemon/src/core/repos/webTokens.ts
652
- import { createHash as createHash2, randomBytes as randomBytes2, randomUUID as randomUUID9 } from "crypto";
667
+ import { createHash as createHash2, randomBytes as randomBytes2, randomUUID as randomUUID11 } from "crypto";
653
668
 
654
669
  // ../daemon/src/core/secrets.ts
655
670
  import { existsSync as existsSync7, readFileSync as readFileSync6 } from "fs";
@@ -695,20 +710,20 @@ function parseSkillFile(path) {
695
710
  }
696
711
 
697
712
  // ../daemon/src/core/team/delete.ts
698
- import { randomUUID as randomUUID10 } from "crypto";
713
+ import { randomUUID as randomUUID12 } from "crypto";
699
714
  import { existsSync as existsSync9, renameSync as renameSync2, rmSync as rmSync6 } from "fs";
700
715
 
701
716
  // ../daemon/src/core/team-policy/adopt.ts
702
- import { randomUUID as randomUUID11 } from "crypto";
717
+ import { randomUUID as randomUUID13 } from "crypto";
703
718
 
704
719
  // ../daemon/src/core/team-policy/authorization.ts
705
- import { createHash as createHash3, randomUUID as randomUUID12 } from "crypto";
720
+ import { createHash as createHash3, randomUUID as randomUUID14 } from "crypto";
706
721
 
707
722
  // ../daemon/src/core/team-policy/source.ts
708
- import { randomUUID as randomUUID13 } from "crypto";
723
+ import { randomUUID as randomUUID15 } from "crypto";
709
724
 
710
725
  // ../daemon/src/core/team-policy/spawn.ts
711
- import { randomUUID as randomUUID14 } from "crypto";
726
+ import { randomUUID as randomUUID16 } from "crypto";
712
727
  import { readdirSync as readdirSync4, rmSync as rmSync8 } from "fs";
713
728
 
714
729
  // ../daemon/src/core/team-policy/rm-with-retry.ts
@@ -1348,11 +1363,34 @@ ${agent.team.userMd.trim()}`
1348
1363
  This team's USER.md is empty. As you learn STABLE facts about the human (preferences, role, working hours, how they like to be addressed), populate it via \`user_md_get\` then \`user_md_write\` (always get first \u2014 you need the etag). Reserve this for things you're confident are durable \u2014 project knowledge belongs in \`memory_write\`, personal notes about yourself in \`home_write\` on IDENTITY.md. **Do NOT send peer messages announcing USER.md changes \u2014 every agent in the team sees the new content in their system prompt on their next turn automatically.**`
1349
1364
  );
1350
1365
  }
1366
+ const reviewedLessons = boundReviewedLessons(agent.privateLessons);
1367
+ if (reviewedLessons.length > 0) {
1368
+ parts.push(
1369
+ `# Reviewed lessons
1370
+
1371
+ These private lessons were explicitly approved by the human for your behavior. Follow them when relevant; current user instructions and higher-priority policy still win.
1372
+
1373
+ ${reviewedLessons.map((lesson) => `- ${lesson}`).join("\n")}`
1374
+ );
1375
+ }
1351
1376
  parts.push(
1352
1377
  "# Memory\n\nYou share a persistent memory backend with every other agent in this team. Use `memory_write` to remember things across sessions, and `memory_search` / `memory_read` / `memory_list` to recall them. This memory is for project knowledge \u2014 codebase notes, decisions, things the user told you about the work. For personal notes about yourself (preferences, persona quirks), use `home_write` on IDENTITY.md instead. Always check memory at the start of a session: another agent in the team may have already learned something useful. **Do NOT send peer messages announcing memory writes \u2014 every agent has access to the same store via `memory_search` and will find your note when they need it.**"
1353
1378
  );
1354
1379
  return parts.join("\n\n---\n\n");
1355
1380
  }
1381
+ function boundReviewedLessons(lessons, maxCharacters = 8e3) {
1382
+ const selected = [];
1383
+ let used = 0;
1384
+ for (const raw of lessons) {
1385
+ const lesson = raw.trim();
1386
+ if (!lesson) continue;
1387
+ const remaining = maxCharacters - used;
1388
+ if (remaining <= 0) break;
1389
+ selected.push(lesson.slice(0, remaining));
1390
+ used += Math.min(lesson.length, remaining);
1391
+ }
1392
+ return selected;
1393
+ }
1356
1394
 
1357
1395
  // ../daemon/src/runtime/shell/tooling.ts
1358
1396
  import { existsSync as existsSync11, lstatSync as lstatSync2, realpathSync as realpathSync2 } from "fs";
@@ -1743,7 +1781,7 @@ function createApprovalGatedBashTool(base, host) {
1743
1781
 
1744
1782
  // ../daemon/src/runtime/shell/docker.ts
1745
1783
  import { spawn } from "child_process";
1746
- import { randomUUID as randomUUID15 } from "crypto";
1784
+ import { randomUUID as randomUUID17 } from "crypto";
1747
1785
  import { closeSync as closeSync2, constants as constants2, mkdtempSync as mkdtempSync2, openSync as openSync2, rmSync as rmSync10, writeFileSync as writeFileSync7 } from "fs";
1748
1786
  import { access, realpath, stat } from "fs/promises";
1749
1787
  import { tmpdir as tmpdir2 } from "os";
@@ -1894,7 +1932,7 @@ function createDockerBashOperations(options) {
1894
1932
  const dockerClientEnv = await resolveLocalDockerClientEnv(dockerPath, hostEnv);
1895
1933
  const resolvedImage = await inspectSandboxImage(dockerPath, image, dockerClientEnv);
1896
1934
  if (signal?.aborted) throw new Error("aborted");
1897
- const containerName = `bazilion-bash-${process.pid}-${randomUUID15().replaceAll("-", "")}`;
1935
+ const containerName = `bazilion-bash-${process.pid}-${randomUUID17().replaceAll("-", "")}`;
1898
1936
  const spec = buildDockerRunSpec({
1899
1937
  image,
1900
1938
  env,
@@ -3668,7 +3706,8 @@ async function createBazilionSession(opts) {
3668
3706
  mcpTools,
3669
3707
  fileSink,
3670
3708
  bashApprovalHost,
3671
- refreshApiKey
3709
+ refreshApiKey,
3710
+ restricted
3672
3711
  } = opts;
3673
3712
  const { providerName, modelId } = splitModelString(agent.model);
3674
3713
  if (enabledProviders.size > 0 && !enabledProviders.has(providerName)) {
@@ -3700,9 +3739,9 @@ async function createBazilionSession(opts) {
3700
3739
  }
3701
3740
  const cwd = agent.team.path;
3702
3741
  if (!existsSync13(cwd)) mkdirSync5(cwd, { recursive: true });
3703
- const promptSkills = loadPromptSkills(paths.skillsDir, agent.skills);
3742
+ const promptSkills = restricted ? [] : loadPromptSkills(paths.skillsDir, agent.skills);
3704
3743
  const uploadsDir = join18(agent.agent.dir, "uploads");
3705
- const shellTools = createSessionShellTools(cwd, env, {
3744
+ const shellTools = restricted ? null : createSessionShellTools(cwd, env, {
3706
3745
  ...existsSync13(uploadsDir) ? { inputsDir: uploadsDir } : {},
3707
3746
  skillMounts: promptSkills.map((skill) => ({
3708
3747
  source: skill.hostDir,
@@ -3710,9 +3749,9 @@ async function createBazilionSession(opts) {
3710
3749
  })),
3711
3750
  approvalHost: bashApprovalHost
3712
3751
  });
3713
- const sessionDir = join18(paths.agentDir(agent.agent.id), "sessions");
3752
+ const sessionDir = restricted?.sessionDir ?? join18(paths.agentDir(agent.agent.id), "sessions");
3714
3753
  mkdirSync5(sessionDir, { recursive: true });
3715
- const existing = findMostRecent(sessionDir);
3754
+ const existing = restricted ? null : findMostRecent(sessionDir);
3716
3755
  const sessionManager = existing ? SessionManager.open(existing, sessionDir, cwd) : SessionManager.create(cwd, sessionDir);
3717
3756
  const settingsManager = SettingsManager.inMemory({
3718
3757
  compaction: { enabled: false },
@@ -3723,13 +3762,13 @@ async function createBazilionSession(opts) {
3723
3762
  provider: { maxRetryDelayMs: 8e3 }
3724
3763
  }
3725
3764
  });
3726
- const bazilionPrompt = buildSystemPrompt(agent, {
3765
+ const bazilionPrompt = restricted?.systemPrompt ?? buildSystemPrompt(agent, {
3727
3766
  skills: promptSkills,
3728
- sandboxMode: shellTools.config.sandboxMode
3767
+ sandboxMode: shellTools?.config.sandboxMode ?? "off"
3729
3768
  });
3730
3769
  const resourceLoader = createBazilionResourceLoader(bazilionPrompt);
3731
3770
  await resourceLoader.reload();
3732
- const bazilionTools = createBazilionCustomTools({
3771
+ const bazilionTools = restricted ? restricted.tools : createBazilionCustomTools({
3733
3772
  agent,
3734
3773
  memory,
3735
3774
  messagingHost,
@@ -3740,13 +3779,13 @@ async function createBazilionSession(opts) {
3740
3779
  fileSink,
3741
3780
  env
3742
3781
  });
3743
- const customTools = shellTools.customBash ? [...bazilionTools, shellTools.customBash] : bazilionTools;
3744
- const allowedTools = [...shellTools.hostToolNames, ...customTools.map((t) => t.name)];
3782
+ const customTools = shellTools?.customBash ? [...bazilionTools, shellTools.customBash] : bazilionTools;
3783
+ const allowedTools = [...shellTools?.hostToolNames ?? [], ...customTools.map((t) => t.name)];
3745
3784
  const { session } = await createAgentSession({
3746
3785
  cwd,
3747
3786
  agentDir: join18(paths.home, "pi"),
3748
3787
  model,
3749
- thinkingLevel: toPiThinkingLevel(agent.reasoningLevel),
3788
+ thinkingLevel: toPiThinkingLevel(restricted?.reasoningLevel ?? agent.reasoningLevel),
3750
3789
  tools: allowedTools,
3751
3790
  customTools,
3752
3791
  sessionManager,
@@ -3902,7 +3941,7 @@ function assertOpenAICodexProvider(requested, expected) {
3902
3941
  }
3903
3942
 
3904
3943
  // ../daemon/src/runtime/worker/ipc-client.ts
3905
- import { randomUUID as randomUUID16 } from "crypto";
3944
+ import { randomUUID as randomUUID18 } from "crypto";
3906
3945
  function createIpcClient(channel) {
3907
3946
  const pending = /* @__PURE__ */ new Map();
3908
3947
  let disconnected = false;
@@ -3929,7 +3968,7 @@ function createIpcClient(channel) {
3929
3968
  if (!send) {
3930
3969
  return Promise.reject(new Error("worker: no IPC channel \u2014 daemon must spawn with stdio:ipc"));
3931
3970
  }
3932
- const id = randomUUID16();
3971
+ const id = randomUUID18();
3933
3972
  const message = { type: "rpc", id, method, args };
3934
3973
  return new Promise((resolve5, reject) => {
3935
3974
  pending.set(id, { resolve: (value) => resolve5(value), reject });
@@ -3968,6 +4007,9 @@ async function readInput() {
3968
4007
  "worker: stdin must include {agent, message, enabledProviders, turnId, bashApprovalMode}"
3969
4008
  );
3970
4009
  }
4010
+ if (parsed.mode === "review" && !parsed.review?.reviewId) {
4011
+ throw new Error("worker: review mode requires review metadata");
4012
+ }
3971
4013
  const providerName = parsed.agent.model.split(":", 1)[0] ?? "";
3972
4014
  if (parsed.apiKeyRefreshEnabled && (providerName !== "openai-codex" || typeof parsed.apiKey !== "string")) {
3973
4015
  throw new Error("worker: API key refresh requires an openai-codex turn with an initial token");
@@ -4006,7 +4048,7 @@ function createIpcBashApprovalHost(call, input) {
4006
4048
  return {
4007
4049
  async requestApproval(request) {
4008
4050
  const result = await call("bashApproval", {
4009
- id: randomUUID17(),
4051
+ id: randomUUID19(),
4010
4052
  turnId: input.turnId,
4011
4053
  toolCallId: request.toolCallId,
4012
4054
  agentId: input.agent.agent.id,
@@ -4046,7 +4088,9 @@ async function main() {
4046
4088
  mcpTools,
4047
4089
  images,
4048
4090
  turnId,
4049
- bashApprovalMode
4091
+ bashApprovalMode,
4092
+ mode = "chat",
4093
+ review
4050
4094
  } = await readInput();
4051
4095
  const paths = resolvePaths();
4052
4096
  const memory = qmdBackend(join19(agent.team.path, "memory"));
@@ -4072,6 +4116,52 @@ async function main() {
4072
4116
  turnId,
4073
4117
  bashApprovalMode
4074
4118
  });
4119
+ const reviewSessionDir = mode === "review" ? mkdtempSync3(join19(paths.logsDir, "review-session-")) : null;
4120
+ const proposals = [];
4121
+ let proposalBatchSubmitted = false;
4122
+ const allowedEvidence = new Set(
4123
+ (review?.evidence ?? []).map((item) => `${item.sessionId}:${item.entryOrdinal}`)
4124
+ );
4125
+ const reviewTools = [
4126
+ {
4127
+ name: "propose_lesson",
4128
+ label: "propose_lesson",
4129
+ description: "Submit zero to five evidence-backed lesson proposals in one final batch. This is the only available action.",
4130
+ parameters: Type3.Object({
4131
+ proposals: Type3.Array(
4132
+ Type3.Object({
4133
+ scope: Type3.Union([Type3.Literal("private"), Type3.Literal("shared")]),
4134
+ text: Type3.String({ minLength: 1, maxLength: 500 }),
4135
+ evidenceEntryIds: Type3.Array(
4136
+ Type3.Object({ sessionId: Type3.String(), entryOrdinal: Type3.Integer({ minimum: 0 }) }),
4137
+ { minItems: 1 }
4138
+ )
4139
+ }),
4140
+ { maxItems: 5 }
4141
+ )
4142
+ }),
4143
+ async execute(_toolCallId, params) {
4144
+ if (proposalBatchSubmitted) throw new Error("propose_lesson may be called only once");
4145
+ proposalBatchSubmitted = true;
4146
+ const input = params;
4147
+ for (const candidate of input.proposals) {
4148
+ const text = candidate.text.trim();
4149
+ if (!text || text.length > 500) throw new Error("proposal text must be 1-500 characters");
4150
+ for (const evidence of candidate.evidenceEntryIds) {
4151
+ if (!allowedEvidence.has(`${evidence.sessionId}:${evidence.entryOrdinal}`)) {
4152
+ throw new Error("proposal cites evidence outside the supplied digest");
4153
+ }
4154
+ }
4155
+ proposals.push({ ...candidate, text });
4156
+ }
4157
+ return {
4158
+ content: [{ type: "text", text: `accepted ${proposals.length} proposals` }],
4159
+ details: {},
4160
+ terminate: true
4161
+ };
4162
+ }
4163
+ }
4164
+ ];
4075
4165
  const { session, dispose } = await createBazilionSession({
4076
4166
  agent,
4077
4167
  paths,
@@ -4087,12 +4177,25 @@ async function main() {
4087
4177
  mcpTools,
4088
4178
  bashApprovalHost,
4089
4179
  // deliver_file emits a `file` event straight onto our stdout frame stream.
4090
- fileSink: (f) => emit({ kind: "event", event: { type: "file", ...f } })
4180
+ fileSink: (f) => emit({ kind: "event", event: { type: "file", ...f } }),
4181
+ ...mode === "review" && reviewSessionDir ? {
4182
+ restricted: {
4183
+ systemPrompt: REVIEW_SYSTEM_PROMPT,
4184
+ tools: reviewTools,
4185
+ sessionDir: reviewSessionDir,
4186
+ reasoningLevel: agent.agent.reviewReasoningLevel
4187
+ }
4188
+ } : {}
4091
4189
  });
4092
4190
  abortSession = () => {
4093
4191
  void session.abort();
4094
4192
  };
4193
+ let reviewValidationFailures = 0;
4095
4194
  const unsubscribe = session.subscribe((piEvent) => {
4195
+ if (mode === "review" && piEvent.type === "tool_execution_end" && piEvent.toolName === "propose_lesson" && piEvent.isError) {
4196
+ reviewValidationFailures += 1;
4197
+ if (reviewValidationFailures >= 2) void session.abort();
4198
+ }
4096
4199
  for (const ev of translatePiEvent(piEvent)) {
4097
4200
  emit({ kind: "event", event: ev });
4098
4201
  }
@@ -4105,10 +4208,19 @@ async function main() {
4105
4208
  }));
4106
4209
  await session.prompt(message, promptImages.length > 0 ? { images: promptImages } : void 0);
4107
4210
  await session.agent.waitForIdle();
4108
- emit({
4109
- kind: "done",
4110
- messages: piMessagesToProviderView(session.agent.state.messages)
4111
- });
4211
+ if (mode === "review" && reviewValidationFailures >= 2) {
4212
+ throw new Error("review output remained invalid after one schema-repair attempt");
4213
+ }
4214
+ if (mode === "review" && !proposalBatchSubmitted) {
4215
+ throw new Error("review output did not submit a proposal batch");
4216
+ }
4217
+ if (mode === "review") emit({ kind: "review_result", proposals });
4218
+ else {
4219
+ emit({
4220
+ kind: "done",
4221
+ messages: piMessagesToProviderView(session.agent.state.messages)
4222
+ });
4223
+ }
4112
4224
  } catch (err) {
4113
4225
  if (!aborted) {
4114
4226
  emit({ kind: "event", event: { type: "error", error: err.message } });
@@ -4120,6 +4232,7 @@ async function main() {
4120
4232
  process.off("SIGTERM", onSignal);
4121
4233
  process.off("SIGINT", onSignal);
4122
4234
  dispose();
4235
+ if (reviewSessionDir) rmSync12(reviewSessionDir, { recursive: true, force: true });
4123
4236
  try {
4124
4237
  process.disconnect?.();
4125
4238
  } catch {
@@ -4133,4 +4246,21 @@ main().catch((err) => {
4133
4246
  }
4134
4247
  process.exit(1);
4135
4248
  });
4249
+ var REVIEW_SYSTEM_PROMPT = `You are a restricted learning reviewer. Analyze only the supplied
4250
+ transcript digest and approved-lesson index. You cannot act on the world. Call propose_lesson once
4251
+ with zero to five proposals, then stop.
4252
+
4253
+ If propose_lesson rejects malformed output, correct the schema exactly once. If that repair is also
4254
+ rejected, stop; the review will fail.
4255
+
4256
+ Classify before proposing:
4257
+ 1. Stable human preference or biography belongs to USER.md: do not propose it.
4258
+ 2. Agent-specific verified behavior or strategy is private.
4259
+ 3. Reusable verified Team project knowledge, decisions, or procedures are shared.
4260
+ 4. Everything else produces no proposal.
4261
+
4262
+ Zero proposals is a successful result. Never save transient or environment-dependent failures,
4263
+ unresolved attempts, guesses, contradicted claims, one-off narratives, status updates, secrets,
4264
+ credentials, sensitive personal data, raw logs, copied text, or duplicates. Each proposal must cite
4265
+ at least one supplied evidence entry and one observation may appear in only one proposal.`;
4136
4266
  //# sourceMappingURL=worker.js.map