@robota-sdk/agent-sdk 3.0.0-beta.56 → 3.0.0-beta.57

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.
@@ -103,6 +103,28 @@ function buildBackgroundSubcommands() {
103
103
  { name: "close", description: "Dismiss a terminal background task", source: "builtin" }
104
104
  ];
105
105
  }
106
+ function buildRewindSubcommands() {
107
+ return [
108
+ { name: "list", description: "List edit checkpoints", source: "builtin" },
109
+ { name: "restore", description: "Restore code to a checkpoint", source: "builtin" },
110
+ { name: "code", description: "Restore code to a checkpoint", source: "builtin" }
111
+ ];
112
+ }
113
+ function buildMemorySubcommands() {
114
+ return [
115
+ { name: "list", description: "List project memory topics", source: "builtin" },
116
+ { name: "show", description: "Show project memory index or a topic", source: "builtin" },
117
+ { name: "add", description: "Save durable project memory", source: "builtin" },
118
+ { name: "pending", description: "List pending memory candidates", source: "builtin" },
119
+ { name: "approve", description: "Approve a pending memory candidate", source: "builtin" },
120
+ { name: "reject", description: "Reject a pending memory candidate", source: "builtin" },
121
+ {
122
+ name: "used",
123
+ description: "Show memory references used in the current turn",
124
+ source: "builtin"
125
+ }
126
+ ];
127
+ }
106
128
  function createBuiltinCommands() {
107
129
  return [
108
130
  { name: "help", description: "Show available commands", source: "builtin" },
@@ -139,6 +161,18 @@ function createBuiltinCommands() {
139
161
  { name: "cost", description: "Show session info", source: "builtin" },
140
162
  { name: "context", description: "Context window info", source: "builtin" },
141
163
  { name: "permissions", description: "Permission rules", source: "builtin" },
164
+ {
165
+ name: "memory",
166
+ description: "Inspect, save, review, and audit project memory",
167
+ source: "builtin",
168
+ subcommands: buildMemorySubcommands()
169
+ },
170
+ {
171
+ name: "rewind",
172
+ description: "List and restore edit checkpoints",
173
+ source: "builtin",
174
+ subcommands: buildRewindSubcommands()
175
+ },
142
176
  {
143
177
  name: "provider",
144
178
  description: "Manage provider profiles",
@@ -385,8 +419,509 @@ Next offset: ${page.nextCursor.offset}` : "";
385
419
  return { message: `Unknown background action: ${action}`, success: false };
386
420
  }
387
421
 
422
+ // src/memory/pending-memory-store.ts
423
+ import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
424
+ import { dirname, join as join2 } from "path";
425
+ var PENDING_FILENAME = "pending.json";
426
+ function memoryRoot(cwd) {
427
+ return join2(cwd, ".robota", "memory");
428
+ }
429
+ function emptyDocument() {
430
+ return { version: 1, records: [] };
431
+ }
432
+ var PendingMemoryStore = class {
433
+ path;
434
+ now;
435
+ constructor(cwd, now = () => /* @__PURE__ */ new Date()) {
436
+ this.path = join2(memoryRoot(cwd), PENDING_FILENAME);
437
+ this.now = now;
438
+ }
439
+ getPath() {
440
+ return this.path;
441
+ }
442
+ list(status) {
443
+ const records = this.read().records;
444
+ return status ? records.filter((record) => record.status === status) : records;
445
+ }
446
+ get(id) {
447
+ return this.read().records.find((record) => record.id === id);
448
+ }
449
+ upsert(candidate, status, reason) {
450
+ const document = this.read();
451
+ const updatedAt = this.now().toISOString();
452
+ const existingIndex = document.records.findIndex((record2) => record2.id === candidate.id);
453
+ const record = {
454
+ ...candidate,
455
+ status,
456
+ updatedAt,
457
+ decisionReason: reason
458
+ };
459
+ if (existingIndex >= 0) {
460
+ document.records[existingIndex] = { ...document.records[existingIndex], ...record };
461
+ } else {
462
+ document.records.push(record);
463
+ }
464
+ this.write(document);
465
+ }
466
+ mark(id, status, reason) {
467
+ const document = this.read();
468
+ const index = document.records.findIndex((record2) => record2.id === id);
469
+ if (index < 0) throw new Error(`Memory candidate not found: ${id}`);
470
+ const record = {
471
+ ...document.records[index],
472
+ status,
473
+ updatedAt: this.now().toISOString(),
474
+ decisionReason: reason
475
+ };
476
+ document.records[index] = record;
477
+ this.write(document);
478
+ return record;
479
+ }
480
+ read() {
481
+ if (!existsSync2(this.path)) return emptyDocument();
482
+ try {
483
+ const parsed = JSON.parse(readFileSync2(this.path, "utf8"));
484
+ return { version: 1, records: parsed.records ?? [] };
485
+ } catch {
486
+ return emptyDocument();
487
+ }
488
+ }
489
+ write(document) {
490
+ mkdirSync(dirname(this.path), { recursive: true });
491
+ writeFileSync(this.path, JSON.stringify(document, null, 2), "utf8");
492
+ }
493
+ };
494
+
495
+ // src/memory/project-memory-store.ts
496
+ import {
497
+ existsSync as existsSync3,
498
+ mkdirSync as mkdirSync2,
499
+ readdirSync as readdirSync2,
500
+ readFileSync as readFileSync3,
501
+ writeFileSync as writeFileSync2,
502
+ appendFileSync
503
+ } from "fs";
504
+ import { basename as basename2, join as join3 } from "path";
505
+ var MEMORY_INDEX_MAX_LINES = 200;
506
+ var MEMORY_INDEX_MAX_BYTES = Number("25600");
507
+ var INDEX_FILENAME = "MEMORY.md";
508
+ var TOPICS_DIRNAME = "topics";
509
+ var DATE_LENGTH = 10;
510
+ var MAX_TOPIC_LENGTH = 80;
511
+ var DEFAULT_TOPIC = "general";
512
+ var TOPIC_EXTENSION = ".md";
513
+ var VALID_TYPES = ["user", "feedback", "project", "reference"];
514
+ function isMemoryType(value) {
515
+ return VALID_TYPES.includes(value);
516
+ }
517
+ function memoryRoot2(cwd) {
518
+ return join3(cwd, ".robota", "memory");
519
+ }
520
+ function truncateToUtf8Bytes(value, maxBytes) {
521
+ const buffer = Buffer.from(value, "utf8");
522
+ if (buffer.byteLength <= maxBytes) return value;
523
+ return buffer.subarray(0, maxBytes).toString("utf8");
524
+ }
525
+ function limitLines(value, maxLines) {
526
+ const lines = value.split(/\r?\n/);
527
+ const limited = lines.slice(0, maxLines);
528
+ return {
529
+ content: limited.join("\n").trimEnd(),
530
+ truncated: lines.length > maxLines
531
+ };
532
+ }
533
+ function sanitizeTopic(topic) {
534
+ const normalized = topic.trim().toLowerCase().replace(/[^a-z0-9가-힣_-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, MAX_TOPIC_LENGTH);
535
+ return normalized || DEFAULT_TOPIC;
536
+ }
537
+ function formatEntry(date, input, topic) {
538
+ const day = date.toISOString().slice(0, DATE_LENGTH);
539
+ const text = input.text.trim().replace(/\s+/g, " ");
540
+ return `[${day}] (${input.type}/${topic}) ${text}`;
541
+ }
542
+ function normalizeMemoryText(text) {
543
+ return text.trim().replace(/\s+/g, " ");
544
+ }
545
+ var ProjectMemoryStore = class {
546
+ cwd;
547
+ now;
548
+ constructor(cwd, now = () => /* @__PURE__ */ new Date()) {
549
+ this.cwd = cwd;
550
+ this.now = now;
551
+ }
552
+ getIndexPath() {
553
+ return join3(memoryRoot2(this.cwd), INDEX_FILENAME);
554
+ }
555
+ getTopicsPath() {
556
+ return join3(memoryRoot2(this.cwd), TOPICS_DIRNAME);
557
+ }
558
+ loadStartupMemory() {
559
+ const path = this.getIndexPath();
560
+ if (!existsSync3(path)) {
561
+ return { content: "", path, lineCount: 0, truncated: false };
562
+ }
563
+ const raw = readFileSync3(path, "utf8");
564
+ const byBytes = truncateToUtf8Bytes(raw, MEMORY_INDEX_MAX_BYTES);
565
+ const byteTruncated = Buffer.byteLength(raw, "utf8") > MEMORY_INDEX_MAX_BYTES;
566
+ const byLines = limitLines(byBytes, MEMORY_INDEX_MAX_LINES);
567
+ return {
568
+ content: byLines.content,
569
+ path,
570
+ lineCount: byLines.content.length === 0 ? 0 : byLines.content.split(/\r?\n/).length,
571
+ truncated: byteTruncated || byLines.truncated
572
+ };
573
+ }
574
+ list() {
575
+ const topicsPath = this.getTopicsPath();
576
+ const topics = existsSync3(topicsPath) ? readdirSync2(topicsPath, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(TOPIC_EXTENSION)).map((entry) => ({
577
+ name: basename2(entry.name, TOPIC_EXTENSION),
578
+ path: join3(topicsPath, entry.name)
579
+ })).sort((a, b) => a.name.localeCompare(b.name)) : [];
580
+ return {
581
+ indexPath: this.getIndexPath(),
582
+ topicsPath,
583
+ topics
584
+ };
585
+ }
586
+ readTopic(topic) {
587
+ const normalized = sanitizeTopic(topic);
588
+ const path = join3(this.getTopicsPath(), `${normalized}${TOPIC_EXTENSION}`);
589
+ if (!existsSync3(path)) return "";
590
+ return readFileSync3(path, "utf8").trimEnd();
591
+ }
592
+ append(input) {
593
+ const topic = sanitizeTopic(input.topic);
594
+ const root = memoryRoot2(this.cwd);
595
+ const topicsPath = this.getTopicsPath();
596
+ mkdirSync2(topicsPath, { recursive: true });
597
+ const indexPath = this.getIndexPath();
598
+ const topicPath = join3(topicsPath, `${topic}${TOPIC_EXTENSION}`);
599
+ const entry = formatEntry(this.now(), input, topic);
600
+ const topicHeader = existsSync3(topicPath) ? "" : `# ${topic}
601
+
602
+ `;
603
+ const normalizedText = normalizeMemoryText(input.text);
604
+ if (existsSync3(topicPath) && readFileSync3(topicPath, "utf8").includes(`) ${normalizedText}`)) {
605
+ return { indexPath, topicPath, topic, deduplicated: true };
606
+ }
607
+ if (!existsSync3(indexPath)) {
608
+ mkdirSync2(root, { recursive: true });
609
+ writeFileSync2(indexPath, "# Project Memory\n\n", "utf8");
610
+ }
611
+ appendFileSync(indexPath, `- ${entry}
612
+ `, "utf8");
613
+ appendFileSync(topicPath, `${topicHeader}- ${entry}
614
+ `, "utf8");
615
+ return { indexPath, topicPath, topic, deduplicated: false };
616
+ }
617
+ };
618
+
619
+ // src/memory/memory-policy-evaluator.ts
620
+ var SENSITIVE_PATTERNS = [
621
+ /\b(api[_-]?key|secret|token|password|private key)\b/i,
622
+ /\b\d{3}-\d{2}-\d{4}\b/,
623
+ /\b\d{4}[- ]?\d{4}[- ]?\d{4}[- ]?\d{4}\b/,
624
+ /주민등록|비밀번호|시크릿|토큰/u
625
+ ];
626
+ function containsSensitiveMemoryContent(text) {
627
+ return SENSITIVE_PATTERNS.some((pattern) => pattern.test(text));
628
+ }
629
+
630
+ // src/commands/memory-command.ts
631
+ var SUBCOMMAND_INDEX = 0;
632
+ var TYPE_INDEX = 1;
633
+ var TOPIC_INDEX = 2;
634
+ var TEXT_START_INDEX = 3;
635
+ function usage() {
636
+ return {
637
+ message: "Usage: memory list | memory show [topic] | memory add <user|feedback|project|reference> <topic> <text> | memory pending | memory approve <id> | memory reject <id> | memory used",
638
+ success: false
639
+ };
640
+ }
641
+ function formatList(store) {
642
+ const summary = store.list();
643
+ const topics = summary.topics.length > 0 ? summary.topics.map((topic) => `- ${topic.name}: ${topic.path}`).join("\n") : "(none)";
644
+ return {
645
+ message: [
646
+ `Memory index: ${summary.indexPath}`,
647
+ `Topics directory: ${summary.topicsPath}`,
648
+ "Topics:",
649
+ topics
650
+ ].join("\n"),
651
+ success: true,
652
+ data: {
653
+ indexPath: summary.indexPath,
654
+ topicsPath: summary.topicsPath,
655
+ topicCount: summary.topics.length
656
+ }
657
+ };
658
+ }
659
+ function formatShow(store, topic) {
660
+ if (!topic || topic === "index") {
661
+ const memory = store.loadStartupMemory();
662
+ return {
663
+ message: memory.content || "(empty memory index)",
664
+ success: true,
665
+ data: {
666
+ path: memory.path,
667
+ lineCount: memory.lineCount,
668
+ truncated: memory.truncated
669
+ }
670
+ };
671
+ }
672
+ const content = store.readTopic(topic);
673
+ return {
674
+ message: content || `(empty memory topic: ${topic})`,
675
+ success: true,
676
+ data: { topic }
677
+ };
678
+ }
679
+ function parseAdd(args) {
680
+ const type = args[TYPE_INDEX];
681
+ const topic = args[TOPIC_INDEX];
682
+ const text = args.slice(TEXT_START_INDEX).join(" ").trim();
683
+ if (!type || !isMemoryType(type) || !topic || text.length === 0) return void 0;
684
+ return { type, topic, text };
685
+ }
686
+ function formatPending(store) {
687
+ const records = store.list("pending");
688
+ const lines = records.length > 0 ? records.map(
689
+ (record) => `- ${record.id} ${record.type}/${record.topic} confidence=${record.confidence}: ${record.text}`
690
+ ) : ["(no pending memory candidates)"];
691
+ return {
692
+ message: ["Pending memory candidates:", ...lines].join("\n"),
693
+ success: true,
694
+ data: { count: records.length }
695
+ };
696
+ }
697
+ function recordEvent(session, event) {
698
+ session.recordMemoryEvent({
699
+ ...event,
700
+ at: (/* @__PURE__ */ new Date()).toISOString()
701
+ });
702
+ }
703
+ function approvePending(session, pendingStore, memoryStore, id) {
704
+ if (!id) return usage();
705
+ try {
706
+ const approved = pendingStore.mark(id, "approved", "approved-by-user");
707
+ const saved = memoryStore.append(approved);
708
+ const record = pendingStore.mark(id, "saved", "approved-and-saved");
709
+ recordEvent(session, {
710
+ type: "memory_candidate_approved",
711
+ candidateId: record.id,
712
+ topic: record.topic,
713
+ reason: "approved-by-user"
714
+ });
715
+ recordEvent(session, {
716
+ type: "memory_candidate_saved",
717
+ candidateId: record.id,
718
+ topic: record.topic,
719
+ reason: saved.deduplicated ? "deduplicated" : "approved-and-saved"
720
+ });
721
+ return {
722
+ message: saved.deduplicated ? `Saved memory candidate ${id} was already present in ${saved.topicPath}` : `Saved memory candidate ${id} to ${saved.topicPath}`,
723
+ success: true,
724
+ data: {
725
+ id,
726
+ status: record.status,
727
+ topic: saved.topic,
728
+ topicPath: saved.topicPath,
729
+ deduplicated: saved.deduplicated
730
+ }
731
+ };
732
+ } catch (error) {
733
+ return {
734
+ message: error instanceof Error ? error.message : String(error),
735
+ success: false
736
+ };
737
+ }
738
+ }
739
+ function rejectPending(session, pendingStore, id) {
740
+ if (!id) return usage();
741
+ try {
742
+ const record = pendingStore.mark(id, "rejected", "rejected-by-user");
743
+ recordEvent(session, {
744
+ type: "memory_candidate_rejected",
745
+ candidateId: record.id,
746
+ topic: record.topic,
747
+ reason: "rejected-by-user"
748
+ });
749
+ return {
750
+ message: `Rejected memory candidate ${id}`,
751
+ success: true,
752
+ data: { id, status: record.status }
753
+ };
754
+ } catch (error) {
755
+ return {
756
+ message: error instanceof Error ? error.message : String(error),
757
+ success: false
758
+ };
759
+ }
760
+ }
761
+ function formatUsed(session) {
762
+ const references = session.getUsedMemoryReferences();
763
+ const lines = references.length > 0 ? references.map((reference) => {
764
+ const suffix = reference.truncated ? " truncated=true" : "";
765
+ return `- ${reference.topic} score=${reference.score}${suffix}: ${reference.path}`;
766
+ }) : ["(no memory used in current turn)"];
767
+ return {
768
+ message: ["Used memory references:", ...lines].join("\n"),
769
+ success: true,
770
+ data: { count: references.length, references }
771
+ };
772
+ }
773
+ function executeMemoryCommand(session, rawArgs) {
774
+ const args = rawArgs.trim().split(/\s+/).filter(Boolean);
775
+ const subcommand = args[SUBCOMMAND_INDEX] ?? "list";
776
+ const store = new ProjectMemoryStore(session.getCwd());
777
+ const pendingStore = new PendingMemoryStore(session.getCwd());
778
+ if (subcommand === "list") return formatList(store);
779
+ if (subcommand === "show") return formatShow(store, args[TYPE_INDEX]);
780
+ if (subcommand === "pending") return formatPending(pendingStore);
781
+ if (subcommand === "approve")
782
+ return approvePending(session, pendingStore, store, args[TYPE_INDEX]);
783
+ if (subcommand === "reject") return rejectPending(session, pendingStore, args[TYPE_INDEX]);
784
+ if (subcommand === "used") return formatUsed(session);
785
+ if (subcommand === "add") {
786
+ const input = parseAdd(args);
787
+ if (!input) return usage();
788
+ if (containsSensitiveMemoryContent(input.text)) {
789
+ return {
790
+ message: "Refusing to save sensitive memory content.",
791
+ success: false
792
+ };
793
+ }
794
+ const result = store.append(input);
795
+ return {
796
+ message: result.deduplicated ? `${input.type} memory already exists in ${result.topicPath}` : `Saved ${input.type} memory to ${result.topicPath}`,
797
+ success: true,
798
+ data: {
799
+ indexPath: result.indexPath,
800
+ topicPath: result.topicPath,
801
+ topic: result.topic,
802
+ deduplicated: result.deduplicated
803
+ }
804
+ };
805
+ }
806
+ return usage();
807
+ }
808
+
809
+ // src/commands/rewind-command.ts
810
+ var SUBCOMMAND_INDEX2 = 0;
811
+ var CHECKPOINT_ID_INDEX = 1;
812
+ var PROMPT_PREVIEW_LENGTH = 120;
813
+ var ELLIPSIS_LENGTH = 3;
814
+ function usage2() {
815
+ return {
816
+ message: "Usage: rewind [list] | rewind restore <checkpoint-id> | rewind code <checkpoint-id>",
817
+ success: false
818
+ };
819
+ }
820
+ function formatPrompt(prompt) {
821
+ const compact = prompt.replace(/\s+/g, " ").trim();
822
+ if (compact.length <= PROMPT_PREVIEW_LENGTH) return compact;
823
+ return `${compact.slice(0, PROMPT_PREVIEW_LENGTH - ELLIPSIS_LENGTH)}...`;
824
+ }
825
+ function formatList2(checkpoints) {
826
+ const lines = checkpoints.length > 0 ? checkpoints.map(
827
+ (checkpoint) => `- ${checkpoint.id} files=${checkpoint.fileCount} ${checkpoint.createdAt} ${formatPrompt(
828
+ checkpoint.prompt
829
+ )}`
830
+ ) : ["(no edit checkpoints)"];
831
+ return {
832
+ message: ["Edit checkpoints:", ...lines].join("\n"),
833
+ success: true,
834
+ data: { count: checkpoints.length, checkpoints: [...checkpoints] }
835
+ };
836
+ }
837
+ async function restore(session, checkpointId) {
838
+ if (!checkpointId) return usage2();
839
+ try {
840
+ const result = await session.restoreEditCheckpoint(checkpointId);
841
+ return {
842
+ message: [
843
+ `Restored code to ${result.target.id}.`,
844
+ `Restored files: ${result.restoredFileCount}`,
845
+ `Rolled back checkpoints: ${result.restoredCheckpointCount}`
846
+ ].join("\n"),
847
+ success: true,
848
+ data: {
849
+ target: result.target,
850
+ restoredCheckpointCount: result.restoredCheckpointCount,
851
+ restoredFileCount: result.restoredFileCount,
852
+ removedCheckpointCount: result.removedCheckpointCount
853
+ }
854
+ };
855
+ } catch (error) {
856
+ return {
857
+ message: error instanceof Error ? error.message : String(error),
858
+ success: false
859
+ };
860
+ }
861
+ }
862
+ async function executeRewindCommand(session, rawArgs) {
863
+ const args = rawArgs.trim().split(/\s+/).filter(Boolean);
864
+ const subcommand = args[SUBCOMMAND_INDEX2] ?? "list";
865
+ if (subcommand === "list") {
866
+ return formatList2(session.listEditCheckpoints());
867
+ }
868
+ if (subcommand === "restore" || subcommand === "code") {
869
+ return restore(session, args[CHECKPOINT_ID_INDEX]);
870
+ }
871
+ return usage2();
872
+ }
873
+
874
+ // src/commands/system-command-executor.ts
875
+ var SystemCommandExecutor = class {
876
+ commands;
877
+ constructor(commands) {
878
+ this.commands = /* @__PURE__ */ new Map();
879
+ for (const cmd of commands ?? createSystemCommands()) {
880
+ this.commands.set(cmd.name, cmd);
881
+ }
882
+ }
883
+ /** Register an additional command. */
884
+ register(command) {
885
+ this.commands.set(command.name, command);
886
+ }
887
+ /** Execute a command by name. Returns null if command not found. */
888
+ async execute(name, session, args) {
889
+ const cmd = this.commands.get(name);
890
+ if (!cmd) return null;
891
+ return await cmd.execute(session, args);
892
+ }
893
+ /** List all registered commands. */
894
+ listCommands() {
895
+ return [...this.commands.values()];
896
+ }
897
+ listModelInvocableCommands() {
898
+ return this.listCommands().filter((command) => command.modelInvocable === true).map((command) => ({
899
+ name: `/${command.name}`,
900
+ kind: "builtin-command",
901
+ description: command.description,
902
+ userInvocable: command.userInvocable !== false,
903
+ modelInvocable: true,
904
+ ...command.argumentHint ? { argumentHint: command.argumentHint } : {},
905
+ ...command.safety ? { safety: command.safety } : {}
906
+ }));
907
+ }
908
+ isModelInvocable(name) {
909
+ return this.commands.get(name)?.modelInvocable === true;
910
+ }
911
+ async executeModelInvocable(name, session, args) {
912
+ if (!this.isModelInvocable(name)) return null;
913
+ return this.execute(name, session, args);
914
+ }
915
+ /** Check if a command exists. */
916
+ hasCommand(name) {
917
+ return this.commands.has(name);
918
+ }
919
+ };
920
+
388
921
  // src/commands/system-command.ts
389
922
  var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
923
+ var MEMORY_COMMAND_DESCRIPTION = "Project memory command. Use it to inspect project memory when stored context may help, save durable preferences, project conventions, feedback, or references worth reusing across sessions, review pending candidates, and report memory provenance. Do not store secrets, credentials, or transient facts.";
924
+ var MEMORY_COMMAND_ARGUMENT_HINT = "list | show [topic] | add <user|feedback|project|reference> <topic> <text> | pending | approve <id> | reject <id> | used";
390
925
  function createSystemCommands() {
391
926
  return [
392
927
  {
@@ -404,6 +939,8 @@ function createSystemCommands() {
404
939
  " cost \u2014 Show session info",
405
940
  " context \u2014 Context window info",
406
941
  " permissions \u2014 Permission rules",
942
+ " memory \u2014 Manage project memory and pending candidates",
943
+ " rewind \u2014 List or restore edit checkpoints",
407
944
  " provider \u2014 Provider profile status and switching",
408
945
  " resume \u2014 Resume a previous session",
409
946
  " background \u2014 List/cancel/close background tasks",
@@ -546,6 +1083,21 @@ Messages: ${messageCount}`,
546
1083
  };
547
1084
  }
548
1085
  },
1086
+ {
1087
+ name: "memory",
1088
+ description: MEMORY_COMMAND_DESCRIPTION,
1089
+ modelInvocable: true,
1090
+ argumentHint: MEMORY_COMMAND_ARGUMENT_HINT,
1091
+ safety: "write",
1092
+ execute: executeMemoryCommand
1093
+ },
1094
+ {
1095
+ name: "rewind",
1096
+ description: "List edit checkpoints or restore code to a previous checkpoint.",
1097
+ argumentHint: "list | restore CHECKPOINT_ID | code CHECKPOINT_ID",
1098
+ safety: "write",
1099
+ execute: executeRewindCommand
1100
+ },
549
1101
  {
550
1102
  name: "resume",
551
1103
  description: "Resume a previous session",
@@ -588,51 +1140,6 @@ Messages: ${messageCount}`,
588
1140
  }
589
1141
  ];
590
1142
  }
591
- var SystemCommandExecutor = class {
592
- commands;
593
- constructor(commands) {
594
- this.commands = /* @__PURE__ */ new Map();
595
- for (const cmd of commands ?? createSystemCommands()) {
596
- this.commands.set(cmd.name, cmd);
597
- }
598
- }
599
- /** Register an additional command. */
600
- register(command) {
601
- this.commands.set(command.name, command);
602
- }
603
- /** Execute a command by name. Returns null if command not found. */
604
- async execute(name, session, args) {
605
- const cmd = this.commands.get(name);
606
- if (!cmd) return null;
607
- return await cmd.execute(session, args);
608
- }
609
- /** List all registered commands. */
610
- listCommands() {
611
- return [...this.commands.values()];
612
- }
613
- listModelInvocableCommands() {
614
- return this.listCommands().filter((command) => command.modelInvocable === true).map((command) => ({
615
- name: `/${command.name}`,
616
- kind: "builtin-command",
617
- description: command.description,
618
- userInvocable: command.userInvocable !== false,
619
- modelInvocable: true,
620
- ...command.argumentHint ? { argumentHint: command.argumentHint } : {},
621
- ...command.safety ? { safety: command.safety } : {}
622
- }));
623
- }
624
- isModelInvocable(name) {
625
- return this.commands.get(name)?.modelInvocable === true;
626
- }
627
- async executeModelInvocable(name, session, args) {
628
- if (!this.isModelInvocable(name)) return null;
629
- return this.execute(name, session, args);
630
- }
631
- /** Check if a command exists. */
632
- hasCommand(name) {
633
- return this.commands.has(name);
634
- }
635
- };
636
1143
 
637
1144
  // src/utils/skill-prompt.ts
638
1145
  import { execSync } from "child_process";
@@ -965,11 +1472,11 @@ function createInProcessSubagentRunner(deps) {
965
1472
 
966
1473
  // src/tools/agent-tool.ts
967
1474
  var AGENT_TOOL_DESCRIPTION = [
968
- "Creates one subagent job for delegated work in an isolated context.",
969
- "One tool call creates one subagent job.",
1475
+ "Creates delegated subagent jobs in isolated contexts.",
1476
+ "Without jobs, one tool call creates one subagent job from prompt.",
1477
+ "For explicit multi-agent or parallel-agent requests, use one Agent tool call with jobs containing one entry per requested role.",
970
1478
  "When the user explicitly asks to create, run, spawn, delegate to, or use agents/subagents, start the requested subagent job immediately.",
971
1479
  "Do not ask a follow-up question unless execution is impossible or unsafe.",
972
- "For multiple or parallel agents, create one Agent tool call per requested role in the current turn.",
973
1480
  "Subagent jobs run as background tasks by default.",
974
1481
  "The tool waits for a terminal result and returns completed, failed, or timed-out outcome data to the parent conversation.",
975
1482
  "Execution is represented by a real tool call and runtime background task event."
@@ -977,11 +1484,11 @@ var AGENT_TOOL_DESCRIPTION = [
977
1484
  function createAgentToolPromptDescription(agentDefinitions = []) {
978
1485
  const availableAgents = agentDefinitions.length > 0 ? ` Available agent types: ${agentDefinitions.map((agent) => `${agent.name} (${agent.description})`).join(", ")}.` : "";
979
1486
  return [
980
- "Agent \u2014 creates one isolated subagent job.",
981
- "One Agent tool call corresponds to one subagent job.",
1487
+ "Agent \u2014 creates isolated subagent jobs.",
1488
+ "Without jobs, one Agent tool call corresponds to one subagent job.",
1489
+ "For explicit multi-agent or parallel-agent requests, use one Agent tool call with jobs containing one entry per requested role.",
982
1490
  "When the user explicitly asks to create, run, spawn, delegate to, or use agents/subagents, start the requested subagent job immediately.",
983
1491
  "Do not ask a follow-up question unless execution is impossible or unsafe.",
984
- "For multiple or parallel agents, create one Agent tool call per requested role in the current turn.",
985
1492
  "The tool returns terminal result data.",
986
1493
  "Runtime mode is background.",
987
1494
  availableAgents
@@ -991,10 +1498,18 @@ function asZodSchema(schema) {
991
1498
  return schema;
992
1499
  }
993
1500
  var AgentSchema = z.object({
994
- prompt: z.string().describe("The task for the subagent to perform"),
1501
+ prompt: z.string().optional().describe("The task for a single subagent to perform. Required when jobs is omitted."),
995
1502
  subagent_type: z.string().optional().describe('Agent type: "general-purpose", "Explore", "Plan", or a custom agent name'),
996
1503
  model: z.string().optional().describe("Optional model override"),
997
- isolation: z.enum(["none", "worktree"]).optional().describe('Optional runtime isolation mode. "worktree" runs in a Git worktree.')
1504
+ isolation: z.enum(["none", "worktree"]).optional().describe('Optional runtime isolation mode. "worktree" runs in a Git worktree.'),
1505
+ jobs: z.array(
1506
+ z.object({
1507
+ prompt: z.string().describe("The task for this subagent to perform"),
1508
+ subagent_type: z.string().optional().describe("Agent type for this job"),
1509
+ model: z.string().optional().describe("Optional model override for this job"),
1510
+ isolation: z.enum(["none", "worktree"]).optional().describe("Isolation for this job")
1511
+ }).passthrough()
1512
+ ).optional().describe("Batch of subagent jobs to start in one Agent tool call")
998
1513
  }).passthrough();
999
1514
  var sessionDepsStore = /* @__PURE__ */ new WeakMap();
1000
1515
  function storeAgentToolDeps(key, deps) {
@@ -1057,7 +1572,22 @@ function stringifyAgentError(message, agentId) {
1057
1572
  agentId
1058
1573
  });
1059
1574
  }
1575
+ function stringifyAgentBatchResult(input) {
1576
+ const successfulJobs = input.jobs.filter((job) => job.success);
1577
+ const agentIds = input.jobs.map((job) => job.agentId).filter((agentId) => typeof agentId === "string" && agentId.length > 0);
1578
+ return JSON.stringify({
1579
+ success: input.jobs.every((job) => job.success),
1580
+ output: successfulJobs.map((job) => job.output ?? "").filter(Boolean).join("\n\n"),
1581
+ groupId: input.groupId,
1582
+ agentIds,
1583
+ jobs: input.jobs
1584
+ });
1585
+ }
1060
1586
  async function runManagedAgent(args, deps, manager) {
1587
+ if (typeof args.prompt !== "string" || args.prompt.length === 0) {
1588
+ return stringifyAgentError("prompt is required when jobs is omitted");
1589
+ }
1590
+ const singleArgs = { ...args, prompt: args.prompt };
1061
1591
  const agentType = args.subagent_type ?? "general-purpose";
1062
1592
  const agentDef = resolveAgentDefinition2(agentType, deps.customAgentRegistry);
1063
1593
  if (!agentDef) {
@@ -1065,7 +1595,7 @@ async function runManagedAgent(args, deps, manager) {
1065
1595
  }
1066
1596
  let agentId;
1067
1597
  try {
1068
- const state = await manager.spawn(createSpawnRequest(args, agentType, agentDef, deps));
1598
+ const state = await manager.spawn(createSpawnRequest(singleArgs, agentType, agentDef, deps));
1069
1599
  agentId = state.id;
1070
1600
  const response = await manager.wait(state.id);
1071
1601
  return stringifyAgentSuccess(response);
@@ -1074,21 +1604,93 @@ async function runManagedAgent(args, deps, manager) {
1074
1604
  return stringifyAgentError(message, agentId);
1075
1605
  }
1076
1606
  }
1077
- function createAgentTool(deps) {
1078
- const manager = createSubagentManager(deps);
1079
- return createZodFunctionTool(
1080
- "Agent",
1081
- AGENT_TOOL_DESCRIPTION,
1082
- asZodSchema(AgentSchema),
1083
- async (params) => {
1084
- const args = params;
1085
- return runManagedAgent(
1086
- {
1087
- prompt: args.prompt,
1088
- ...args.subagent_type !== void 0 ? { subagent_type: args.subagent_type } : {},
1089
- ...args.model !== void 0 ? { model: args.model } : {},
1090
- ...args.isolation !== void 0 ? { isolation: args.isolation } : {}
1091
- },
1607
+ function createBatchGroupId() {
1608
+ return `agent_group_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
1609
+ }
1610
+ async function runManagedAgentBatch(jobs, deps, manager) {
1611
+ const groupId = createBatchGroupId();
1612
+ const resolvedJobs = jobs.map((job, index) => {
1613
+ const agentType = job.subagent_type ?? "general-purpose";
1614
+ const agentDef = resolveAgentDefinition2(agentType, deps.customAgentRegistry);
1615
+ return { index, job, agentType, agentDef };
1616
+ });
1617
+ const invalidJobs = resolvedJobs.filter((job) => job.agentDef === void 0).map((job) => ({
1618
+ index: job.index,
1619
+ success: false,
1620
+ subagent_type: job.agentType,
1621
+ error: `Unknown agent type: ${job.agentType}`
1622
+ }));
1623
+ const validJobs = resolvedJobs.filter(
1624
+ (job) => job.agentDef !== void 0
1625
+ );
1626
+ const startedJobs = await Promise.all(
1627
+ validJobs.map(async (job) => {
1628
+ try {
1629
+ const state = await manager.spawn(
1630
+ createSpawnRequest(job.job, job.agentType, job.agentDef, deps)
1631
+ );
1632
+ return { ...job, agentId: state.id, spawnError: void 0 };
1633
+ } catch (error) {
1634
+ const message = error instanceof Error ? error.message : String(error);
1635
+ return { ...job, agentId: void 0, spawnError: message };
1636
+ }
1637
+ })
1638
+ );
1639
+ const terminalJobs = await Promise.all(
1640
+ startedJobs.map(async (job) => {
1641
+ if (job.spawnError || !job.agentId) {
1642
+ return {
1643
+ index: job.index,
1644
+ success: false,
1645
+ subagent_type: job.agentType,
1646
+ error: `Sub-agent error: ${job.spawnError ?? "missing agent id"}`
1647
+ };
1648
+ }
1649
+ try {
1650
+ const result = await manager.wait(job.agentId);
1651
+ return {
1652
+ index: job.index,
1653
+ success: true,
1654
+ agentId: result.jobId,
1655
+ subagent_type: job.agentType,
1656
+ output: result.output,
1657
+ metadata: result.metadata
1658
+ };
1659
+ } catch (error) {
1660
+ const message = error instanceof Error ? error.message : String(error);
1661
+ return {
1662
+ index: job.index,
1663
+ success: false,
1664
+ agentId: job.agentId,
1665
+ subagent_type: job.agentType,
1666
+ error: `Sub-agent error: ${message}`
1667
+ };
1668
+ }
1669
+ })
1670
+ );
1671
+ const batchJobs = [...invalidJobs, ...terminalJobs].sort(
1672
+ (left, right) => left.index - right.index
1673
+ );
1674
+ return stringifyAgentBatchResult({ groupId, jobs: batchJobs });
1675
+ }
1676
+ function createAgentTool(deps) {
1677
+ const manager = createSubagentManager(deps);
1678
+ return createZodFunctionTool(
1679
+ "Agent",
1680
+ AGENT_TOOL_DESCRIPTION,
1681
+ asZodSchema(AgentSchema),
1682
+ async (params) => {
1683
+ const args = params;
1684
+ if (Array.isArray(args.jobs) && args.jobs.length > 0) {
1685
+ return runManagedAgentBatch(args.jobs, deps, manager);
1686
+ }
1687
+ return runManagedAgent(
1688
+ {
1689
+ prompt: args.prompt,
1690
+ ...args.subagent_type !== void 0 ? { subagent_type: args.subagent_type } : {},
1691
+ ...args.model !== void 0 ? { model: args.model } : {},
1692
+ ...args.isolation !== void 0 ? { isolation: args.isolation } : {}
1693
+ },
1092
1694
  deps,
1093
1695
  manager
1094
1696
  );
@@ -1174,8 +1776,8 @@ var BackgroundJobOrchestrator = class {
1174
1776
  createRecord(state) {
1175
1777
  let resolveGroup = () => {
1176
1778
  };
1177
- const completion = new Promise((resolve2) => {
1178
- resolveGroup = resolve2;
1779
+ const completion = new Promise((resolve4) => {
1780
+ resolveGroup = resolve4;
1179
1781
  });
1180
1782
  return { state, completion, resolve: resolveGroup };
1181
1783
  }
@@ -1304,6 +1906,8 @@ function retrieveSessionBackgroundTaskManager(key) {
1304
1906
  }
1305
1907
 
1306
1908
  // src/interactive/interactive-session-execution.ts
1909
+ import { randomUUID } from "crypto";
1910
+ import { collectAssistantUsageMetadata } from "@robota-sdk/agent-core";
1307
1911
  function isAbortError(err) {
1308
1912
  return err instanceof DOMException && err.name === "AbortError" || err instanceof Error && (err.message.includes("aborted") || err.message.includes("abort"));
1309
1913
  }
@@ -1321,11 +1925,13 @@ function extractToolSummaries(history, historyBefore) {
1321
1925
  }
1322
1926
  function buildResult(response, sessionHistory, interactiveHistory, historyBefore, contextState) {
1323
1927
  const toolSummaries = extractToolSummaries(sessionHistory, historyBefore);
1928
+ const usage3 = extractTurnUsage(sessionHistory, historyBefore, contextState);
1324
1929
  return {
1325
1930
  response,
1326
1931
  history: interactiveHistory,
1327
1932
  toolSummaries,
1328
- contextState
1933
+ contextState,
1934
+ ...usage3 && { usage: usage3 }
1329
1935
  };
1330
1936
  }
1331
1937
  function buildInterruptedResult(sessionHistory, interactiveHistory, historyBefore, contextState) {
@@ -1335,14 +1941,51 @@ function buildInterruptedResult(sessionHistory, interactiveHistory, historyBefor
1335
1941
  const msg = sessionHistory[i];
1336
1942
  if (msg?.role === "assistant" && msg.content) parts.push(msg.content);
1337
1943
  }
1944
+ const usage3 = extractTurnUsage(sessionHistory, historyBefore, contextState);
1338
1945
  return {
1339
1946
  response: parts.join("\n\n"),
1340
1947
  history: interactiveHistory,
1341
1948
  toolSummaries,
1342
- contextState
1949
+ contextState,
1950
+ ...usage3 && { usage: usage3 }
1343
1951
  };
1344
1952
  }
1345
- function persistSession(sessionStore, session, sessionName, cwd, history, backgroundState) {
1953
+ function createUsageSummaryEntry(usage3) {
1954
+ return {
1955
+ id: `usage_${randomUUID()}`,
1956
+ timestamp: /* @__PURE__ */ new Date(),
1957
+ category: "event",
1958
+ type: "usage-summary",
1959
+ data: usage3
1960
+ };
1961
+ }
1962
+ function extractTurnUsage(sessionHistory, historyBefore, contextState) {
1963
+ const turnMessages = sessionHistory.slice(historyBefore);
1964
+ let promptTokens = 0;
1965
+ let completionTokens = 0;
1966
+ let foundUsage = false;
1967
+ for (const message of turnMessages) {
1968
+ if (message.role !== "assistant") continue;
1969
+ const usage3 = collectAssistantUsageMetadata(message);
1970
+ if (!usage3) continue;
1971
+ foundUsage = true;
1972
+ promptTokens += usage3.inputTokens;
1973
+ completionTokens += usage3.outputTokens;
1974
+ }
1975
+ if (!foundUsage) return void 0;
1976
+ return {
1977
+ kind: "exact",
1978
+ scope: "turn",
1979
+ promptTokens,
1980
+ completionTokens,
1981
+ totalTokens: promptTokens + completionTokens,
1982
+ contextUsedTokens: contextState.usedTokens,
1983
+ contextMaxTokens: contextState.maxTokens,
1984
+ contextUsedPercentage: contextState.usedPercentage,
1985
+ costStatus: "unknown"
1986
+ };
1987
+ }
1988
+ function persistSession(sessionStore, session, sessionName, cwd, history, backgroundState, memoryState) {
1346
1989
  try {
1347
1990
  const sessionId = session.getSessionId();
1348
1991
  const existing = sessionStore.load(sessionId);
@@ -1361,6 +2004,10 @@ function persistSession(sessionStore, session, sessionName, cwd, history, backgr
1361
2004
  backgroundTaskEvents: [...backgroundState.events],
1362
2005
  backgroundJobGroups: [...backgroundState.groups ?? []],
1363
2006
  backgroundJobGroupEvents: [...backgroundState.groupEvents ?? []]
2007
+ } : {},
2008
+ ...memoryState ? {
2009
+ memoryEvents: [...memoryState.events],
2010
+ usedMemoryReferences: [...memoryState.usedReferences]
1364
2011
  } : {}
1365
2012
  });
1366
2013
  } catch {
@@ -1383,17 +2030,97 @@ var NOOP_TERMINAL = {
1383
2030
  };
1384
2031
 
1385
2032
  // src/interactive/interactive-session-streaming.ts
1386
- import { randomUUID } from "crypto";
2033
+ import { randomUUID as randomUUID2 } from "crypto";
2034
+ import { readFileSync as readFileSync4 } from "fs";
1387
2035
  var TOOL_ARG_DISPLAY_MAX = 80;
1388
2036
  var TAIL_KEEP = 30;
1389
2037
  var MAX_COMPLETED_TOOLS = 50;
1390
2038
  var STREAMING_FLUSH_INTERVAL_MS = 16;
2039
+ var DEFAULT_START_LINE = 1;
2040
+ var EDIT_DIFF_CONTEXT_LINES = 3;
1391
2041
  function extractFirstArg2(toolArgs) {
1392
2042
  if (!toolArgs) return "";
1393
2043
  const firstVal = Object.values(toolArgs)[0];
1394
2044
  const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
1395
2045
  return raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_DISPLAY_MAX - TAIL_KEEP - 3) + "..." + raw.slice(-TAIL_KEEP) : raw;
1396
2046
  }
2047
+ function getStringArg(args, snake, camel) {
2048
+ const value = args?.[snake] ?? args?.[camel];
2049
+ return typeof value === "string" ? value : null;
2050
+ }
2051
+ function parseStartLine(toolResultData) {
2052
+ if (!toolResultData) return DEFAULT_START_LINE;
2053
+ try {
2054
+ const parsed = JSON.parse(toolResultData);
2055
+ return typeof parsed.startLine === "number" && Number.isFinite(parsed.startLine) ? parsed.startLine : DEFAULT_START_LINE;
2056
+ } catch {
2057
+ return DEFAULT_START_LINE;
2058
+ }
2059
+ }
2060
+ function buildEditDiffState(event) {
2061
+ if (event.toolName !== "Edit") return {};
2062
+ const filePath = getStringArg(event.toolArgs, "file_path", "filePath");
2063
+ const oldString = getStringArg(event.toolArgs, "old_string", "oldString");
2064
+ const newString = getStringArg(event.toolArgs, "new_string", "newString");
2065
+ if (!filePath || oldString === null || newString === null || oldString === newString) return {};
2066
+ const startLine = parseStartLine(event.toolResultData);
2067
+ return {
2068
+ diffFile: filePath,
2069
+ diffLines: buildEditDiffLinesWithContext(oldString, newString, startLine, filePath)
2070
+ };
2071
+ }
2072
+ function buildEditDiffLines(oldString, newString, startLine) {
2073
+ return [
2074
+ ...oldString.split("\n").map((text, index) => ({
2075
+ type: "remove",
2076
+ text,
2077
+ lineNumber: startLine + index
2078
+ })),
2079
+ ...newString.split("\n").map((text, index) => ({
2080
+ type: "add",
2081
+ text,
2082
+ lineNumber: startLine + index
2083
+ }))
2084
+ ];
2085
+ }
2086
+ function buildEditDiffLinesWithContext(oldString, newString, startLine, filePath) {
2087
+ const diffLines = buildEditDiffLines(oldString, newString, startLine);
2088
+ let fileLines;
2089
+ try {
2090
+ fileLines = readFileSync4(filePath, "utf8").split("\n");
2091
+ } catch {
2092
+ return diffLines;
2093
+ }
2094
+ const beforeContext = [];
2095
+ const contextStart = Math.max(0, startLine - 1 - EDIT_DIFF_CONTEXT_LINES);
2096
+ for (let index = contextStart; index < startLine - 1; index++) {
2097
+ if (index < fileLines.length) {
2098
+ beforeContext.push({ type: "context", text: fileLines[index], lineNumber: index + 1 });
2099
+ }
2100
+ }
2101
+ const afterContext = [];
2102
+ const afterStart = startLine - 1 + newString.split("\n").length;
2103
+ for (let index = afterStart; index < afterStart + EDIT_DIFF_CONTEXT_LINES; index++) {
2104
+ if (index < fileLines.length) {
2105
+ afterContext.push({ type: "context", text: fileLines[index], lineNumber: index + 1 });
2106
+ }
2107
+ }
2108
+ const hunkStart = beforeContext[0]?.lineNumber ?? diffLines[0]?.lineNumber ?? afterContext[0]?.lineNumber ?? startLine;
2109
+ const oldLineCount = oldString.split("\n").length;
2110
+ const newLineCount = newString.split("\n").length;
2111
+ const oldHunkLineCount = beforeContext.length + oldLineCount + afterContext.length;
2112
+ const newHunkLineCount = beforeContext.length + newLineCount + afterContext.length;
2113
+ return [
2114
+ {
2115
+ type: "hunk",
2116
+ text: `@@ -${hunkStart},${oldHunkLineCount} +${hunkStart},${newHunkLineCount} @@`,
2117
+ lineNumber: hunkStart
2118
+ },
2119
+ ...beforeContext,
2120
+ ...diffLines,
2121
+ ...afterContext
2122
+ ];
2123
+ }
1397
2124
  function pushToolSummaryToHistory(state) {
1398
2125
  if (state.activeTools.length === 0) return;
1399
2126
  const summary = state.activeTools.map((t) => {
@@ -1401,7 +2128,7 @@ function pushToolSummaryToHistory(state) {
1401
2128
  return `${status} ${t.toolName}${t.firstArg ? `(${t.firstArg})` : ""}`;
1402
2129
  }).join("\n");
1403
2130
  state.history.push({
1404
- id: randomUUID(),
2131
+ id: randomUUID2(),
1405
2132
  timestamp: /* @__PURE__ */ new Date(),
1406
2133
  category: "event",
1407
2134
  type: "tool-summary",
@@ -1410,7 +2137,10 @@ function pushToolSummaryToHistory(state) {
1410
2137
  toolName: t.toolName,
1411
2138
  firstArg: t.firstArg,
1412
2139
  isRunning: t.isRunning,
1413
- result: t.result
2140
+ result: t.result,
2141
+ diffFile: t.diffFile,
2142
+ diffLines: t.diffLines,
2143
+ toolResultData: t.toolResultData
1414
2144
  })),
1415
2145
  summary
1416
2146
  }
@@ -1434,7 +2164,7 @@ function applyToolStart(state, event) {
1434
2164
  const toolState = { toolName: event.toolName, firstArg, isRunning: true };
1435
2165
  state.activeTools.push(toolState);
1436
2166
  state.history.push({
1437
- id: randomUUID(),
2167
+ id: randomUUID2(),
1438
2168
  timestamp: /* @__PURE__ */ new Date(),
1439
2169
  category: "event",
1440
2170
  type: "tool-start",
@@ -1446,11 +2176,17 @@ function applyToolEnd(state, event) {
1446
2176
  const result = event.denied ? "denied" : event.success === false ? "error" : "success";
1447
2177
  const idx = state.activeTools.findIndex((t) => t.toolName === event.toolName && t.isRunning);
1448
2178
  if (idx === -1) return null;
1449
- const finished = { ...state.activeTools[idx], isRunning: false, result };
2179
+ const finished = {
2180
+ ...state.activeTools[idx],
2181
+ ...buildEditDiffState(event),
2182
+ isRunning: false,
2183
+ result,
2184
+ toolResultData: event.toolResultData
2185
+ };
1450
2186
  state.activeTools[idx] = finished;
1451
2187
  state.activeTools = trimCompletedTools(state.activeTools);
1452
2188
  state.history.push({
1453
- id: randomUUID(),
2189
+ id: randomUUID2(),
1454
2190
  timestamp: /* @__PURE__ */ new Date(),
1455
2191
  category: "event",
1456
2192
  type: "tool-end",
@@ -1458,12 +2194,307 @@ function applyToolEnd(state, event) {
1458
2194
  toolName: finished.toolName,
1459
2195
  firstArg: finished.firstArg,
1460
2196
  isRunning: false,
1461
- result
2197
+ result,
2198
+ toolResultData: event.toolResultData
1462
2199
  }
1463
2200
  });
1464
2201
  return finished;
1465
2202
  }
1466
2203
 
2204
+ // src/config/config-loader.ts
2205
+ import { readFileSync as readFileSync5, existsSync as existsSync4 } from "fs";
2206
+ import { join as join4 } from "path";
2207
+
2208
+ // src/config/config-types.ts
2209
+ import { z as z2 } from "zod";
2210
+ var UniversalValueSchema = z2.lazy(
2211
+ () => z2.union([
2212
+ z2.string(),
2213
+ z2.number(),
2214
+ z2.boolean(),
2215
+ z2.null(),
2216
+ z2.undefined(),
2217
+ z2.date(),
2218
+ z2.array(UniversalValueSchema),
2219
+ z2.record(UniversalValueSchema)
2220
+ ])
2221
+ );
2222
+ var ProviderSchema = z2.object({
2223
+ name: z2.string().optional(),
2224
+ model: z2.string().optional(),
2225
+ apiKey: z2.string().optional(),
2226
+ baseURL: z2.string().optional(),
2227
+ timeout: z2.number().optional(),
2228
+ options: z2.record(UniversalValueSchema).optional()
2229
+ });
2230
+ var ProviderProfileSchema = z2.object({
2231
+ type: z2.string().optional(),
2232
+ model: z2.string().optional(),
2233
+ apiKey: z2.string().optional(),
2234
+ baseURL: z2.string().optional(),
2235
+ timeout: z2.number().optional(),
2236
+ options: z2.record(UniversalValueSchema).optional()
2237
+ });
2238
+ var PermissionsSchema = z2.object({
2239
+ /** Patterns that are always approved without prompting */
2240
+ allow: z2.array(z2.string()).optional(),
2241
+ /** Patterns that are always denied */
2242
+ deny: z2.array(z2.string()).optional()
2243
+ });
2244
+ var EnvSchema = z2.record(z2.string()).optional();
2245
+ var CommandHookDefinitionSchema = z2.object({
2246
+ type: z2.literal("command"),
2247
+ command: z2.string(),
2248
+ timeout: z2.number().optional()
2249
+ });
2250
+ var HttpHookDefinitionSchema = z2.object({
2251
+ type: z2.literal("http"),
2252
+ url: z2.string(),
2253
+ headers: z2.record(z2.string()).optional(),
2254
+ timeout: z2.number().optional()
2255
+ });
2256
+ var PromptHookDefinitionSchema = z2.object({
2257
+ type: z2.literal("prompt"),
2258
+ prompt: z2.string(),
2259
+ model: z2.string().optional()
2260
+ });
2261
+ var AgentHookDefinitionSchema = z2.object({
2262
+ type: z2.literal("agent"),
2263
+ agent: z2.string(),
2264
+ maxTurns: z2.number().optional(),
2265
+ timeout: z2.number().optional()
2266
+ });
2267
+ var HookDefinitionSchema = z2.discriminatedUnion("type", [
2268
+ CommandHookDefinitionSchema,
2269
+ HttpHookDefinitionSchema,
2270
+ PromptHookDefinitionSchema,
2271
+ AgentHookDefinitionSchema
2272
+ ]);
2273
+ var HookGroupSchema = z2.object({
2274
+ matcher: z2.string(),
2275
+ hooks: z2.array(HookDefinitionSchema)
2276
+ });
2277
+ var HooksSchema = z2.object({
2278
+ PreToolUse: z2.array(HookGroupSchema).optional(),
2279
+ PostToolUse: z2.array(HookGroupSchema).optional(),
2280
+ SessionStart: z2.array(HookGroupSchema).optional(),
2281
+ SessionEnd: z2.array(HookGroupSchema).optional(),
2282
+ Stop: z2.array(HookGroupSchema).optional(),
2283
+ StopFailure: z2.array(HookGroupSchema).optional(),
2284
+ PreCompact: z2.array(HookGroupSchema).optional(),
2285
+ PostCompact: z2.array(HookGroupSchema).optional(),
2286
+ UserPromptSubmit: z2.array(HookGroupSchema).optional(),
2287
+ SubagentStart: z2.array(HookGroupSchema).optional(),
2288
+ SubagentStop: z2.array(HookGroupSchema).optional(),
2289
+ WorktreeCreate: z2.array(HookGroupSchema).optional(),
2290
+ WorktreeRemove: z2.array(HookGroupSchema).optional()
2291
+ }).optional();
2292
+ var EnabledPluginsSchema = z2.record(z2.boolean()).optional();
2293
+ var MarketplaceSourceSchema = z2.object({
2294
+ source: z2.object({
2295
+ type: z2.enum(["github", "git", "local", "url"]),
2296
+ repo: z2.string().optional(),
2297
+ url: z2.string().optional(),
2298
+ path: z2.string().optional(),
2299
+ ref: z2.string().optional()
2300
+ })
2301
+ });
2302
+ var ExtraKnownMarketplacesSchema = z2.record(MarketplaceSourceSchema).optional().catch(void 0);
2303
+ var SettingsSchema = z2.object({
2304
+ /** Trust level used when no --permission-mode flag is given */
2305
+ defaultTrustLevel: z2.enum(["safe", "moderate", "full"]).optional(),
2306
+ /** Response language (e.g., "ko", "en", "ja"). Injected into system prompt. */
2307
+ language: z2.string().optional(),
2308
+ /** Active provider profile key from providers. */
2309
+ currentProvider: z2.string().optional(),
2310
+ /** Provider profiles keyed by user-facing profile name. */
2311
+ providers: z2.record(ProviderProfileSchema).optional(),
2312
+ /** Legacy single-provider settings. Prefer currentProvider + providers for new config. */
2313
+ provider: ProviderSchema.optional(),
2314
+ permissions: PermissionsSchema.optional(),
2315
+ env: EnvSchema,
2316
+ hooks: HooksSchema,
2317
+ /** Plugin enablement map: plugin name -> enabled/disabled */
2318
+ enabledPlugins: EnabledPluginsSchema,
2319
+ /** Extra marketplace URLs for BundlePlugin discovery */
2320
+ extraKnownMarketplaces: ExtraKnownMarketplacesSchema
2321
+ });
2322
+
2323
+ // src/config/config-loader.ts
2324
+ function getHomeDir() {
2325
+ return process.env.HOME ?? process.env.USERPROFILE ?? "/";
2326
+ }
2327
+ var DEFAULTS = {
2328
+ defaultTrustLevel: "moderate",
2329
+ provider: {
2330
+ name: "anthropic",
2331
+ model: "claude-opus-4-5",
2332
+ apiKey: void 0
2333
+ },
2334
+ permissions: {
2335
+ allow: [],
2336
+ deny: []
2337
+ },
2338
+ env: {}
2339
+ };
2340
+ function readJsonFile(filePath) {
2341
+ if (!existsSync4(filePath)) {
2342
+ return void 0;
2343
+ }
2344
+ const raw = readFileSync5(filePath, "utf-8").trim();
2345
+ if (raw.length === 0) {
2346
+ return void 0;
2347
+ }
2348
+ try {
2349
+ return JSON.parse(raw);
2350
+ } catch {
2351
+ return void 0;
2352
+ }
2353
+ }
2354
+ function resolveEnvRef(value) {
2355
+ const ENV_PREFIX = "$ENV:";
2356
+ if (value.startsWith(ENV_PREFIX)) {
2357
+ const varName = value.slice(ENV_PREFIX.length);
2358
+ return process.env[varName] ?? value;
2359
+ }
2360
+ return value;
2361
+ }
2362
+ function resolveEnvRefs(settings) {
2363
+ const provider = settings.provider?.apiKey !== void 0 ? {
2364
+ ...settings.provider,
2365
+ apiKey: resolveEnvRef(settings.provider.apiKey)
2366
+ } : settings.provider;
2367
+ if (settings.providers !== void 0) {
2368
+ const providers = Object.fromEntries(
2369
+ Object.entries(settings.providers).map(([name, profile]) => [
2370
+ name,
2371
+ {
2372
+ ...profile,
2373
+ ...profile.apiKey !== void 0 && { apiKey: resolveEnvRef(profile.apiKey) }
2374
+ }
2375
+ ])
2376
+ );
2377
+ return {
2378
+ ...settings,
2379
+ provider,
2380
+ providers
2381
+ };
2382
+ }
2383
+ return {
2384
+ ...settings,
2385
+ provider
2386
+ };
2387
+ }
2388
+ function mergeSettings(layers) {
2389
+ return layers.reduce((merged, layer) => {
2390
+ return {
2391
+ ...merged,
2392
+ ...layer,
2393
+ provider: merged.provider !== void 0 || layer.provider !== void 0 ? { ...merged.provider, ...layer.provider } : void 0,
2394
+ permissions: merged.permissions !== void 0 || layer.permissions !== void 0 ? {
2395
+ allow: layer.permissions?.allow ?? merged.permissions?.allow,
2396
+ deny: layer.permissions?.deny ?? merged.permissions?.deny
2397
+ } : void 0,
2398
+ env: {
2399
+ ...merged.env ?? {},
2400
+ ...layer.env ?? {}
2401
+ },
2402
+ providers: merged.providers !== void 0 || layer.providers !== void 0 ? mergeProviders(merged.providers, layer.providers) : void 0,
2403
+ enabledPlugins: merged.enabledPlugins !== void 0 || layer.enabledPlugins !== void 0 ? { ...merged.enabledPlugins ?? {}, ...layer.enabledPlugins ?? {} } : void 0,
2404
+ extraKnownMarketplaces: layer.extraKnownMarketplaces ?? merged.extraKnownMarketplaces
2405
+ };
2406
+ }, {});
2407
+ }
2408
+ function mergeProviders(base, override) {
2409
+ const result = { ...base ?? {} };
2410
+ for (const [name, profile] of Object.entries(override ?? {})) {
2411
+ result[name] = {
2412
+ ...result[name],
2413
+ ...profile
2414
+ };
2415
+ }
2416
+ return result;
2417
+ }
2418
+ function resolveProvider(merged) {
2419
+ if (merged.currentProvider !== void 0) {
2420
+ const profile = merged.providers?.[merged.currentProvider];
2421
+ if (profile === void 0) {
2422
+ throw new Error(`currentProvider "${merged.currentProvider}" was not found in providers`);
2423
+ }
2424
+ if (profile.type === void 0) {
2425
+ throw new Error(`Provider profile "${merged.currentProvider}" is missing type`);
2426
+ }
2427
+ return {
2428
+ name: profile.type,
2429
+ model: profile.model ?? DEFAULTS.provider.model,
2430
+ apiKey: profile.apiKey ?? DEFAULTS.provider.apiKey,
2431
+ ...profile.baseURL !== void 0 && { baseURL: profile.baseURL },
2432
+ ...profile.timeout !== void 0 && { timeout: profile.timeout },
2433
+ ...profile.options !== void 0 && { options: profile.options }
2434
+ };
2435
+ }
2436
+ return {
2437
+ name: merged.provider?.name ?? DEFAULTS.provider.name,
2438
+ model: merged.provider?.model ?? DEFAULTS.provider.model,
2439
+ apiKey: merged.provider?.apiKey ?? DEFAULTS.provider.apiKey,
2440
+ ...merged.provider?.baseURL !== void 0 && { baseURL: merged.provider.baseURL },
2441
+ ...merged.provider?.timeout !== void 0 && { timeout: merged.provider.timeout },
2442
+ ...merged.provider?.options !== void 0 && { options: merged.provider.options }
2443
+ };
2444
+ }
2445
+ function toResolvedConfig(merged) {
2446
+ return {
2447
+ defaultTrustLevel: merged.defaultTrustLevel ?? DEFAULTS.defaultTrustLevel,
2448
+ language: merged.language,
2449
+ currentProvider: merged.currentProvider,
2450
+ provider: resolveProvider(merged),
2451
+ permissions: {
2452
+ allow: merged.permissions?.allow ?? DEFAULTS.permissions.allow,
2453
+ deny: merged.permissions?.deny ?? DEFAULTS.permissions.deny
2454
+ },
2455
+ env: merged.env ?? DEFAULTS.env,
2456
+ hooks: merged.hooks ?? void 0,
2457
+ enabledPlugins: merged.enabledPlugins ?? void 0,
2458
+ extraKnownMarketplaces: merged.extraKnownMarketplaces ?? void 0
2459
+ };
2460
+ }
2461
+ function getSettingsPaths(cwd) {
2462
+ const home = getHomeDir();
2463
+ return [
2464
+ join4(home, ".robota", "settings.json"),
2465
+ // 1. user (lowest)
2466
+ join4(home, ".claude", "settings.json"),
2467
+ // 1b. user (Claude Code compat)
2468
+ join4(cwd, ".robota", "settings.json"),
2469
+ // 2. project
2470
+ join4(cwd, ".robota", "settings.local.json"),
2471
+ // 3. project-local
2472
+ join4(cwd, ".claude", "settings.json"),
2473
+ // 4. project, Claude Code compat
2474
+ join4(cwd, ".claude", "settings.local.json")
2475
+ // 5. project-local (highest)
2476
+ ];
2477
+ }
2478
+ async function loadConfig(cwd) {
2479
+ const allPaths = getSettingsPaths(cwd);
2480
+ const rawEntries = [];
2481
+ for (const filePath of allPaths) {
2482
+ const raw = readJsonFile(filePath);
2483
+ if (raw !== void 0) {
2484
+ rawEntries.push({ raw, path: filePath });
2485
+ }
2486
+ }
2487
+ const parsedLayers = rawEntries.map(({ raw, path }) => {
2488
+ const result = SettingsSchema.safeParse(raw);
2489
+ if (!result.success) {
2490
+ throw new Error(`Invalid settings in ${path}: ${result.error.message}`);
2491
+ }
2492
+ return resolveEnvRefs(result.data);
2493
+ });
2494
+ const merged = mergeSettings(parsedLayers);
2495
+ return toResolvedConfig(merged);
2496
+ }
2497
+
1467
2498
  // src/hooks/prompt-executor.ts
1468
2499
  function extractJson(raw) {
1469
2500
  const codeBlockMatch = /```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/.exec(raw);
@@ -1590,6 +2621,8 @@ var TRUST_LEVEL_LABELS = {
1590
2621
  moderate: "moderate",
1591
2622
  full: "full"
1592
2623
  };
2624
+ var PROJECT_MEMORY_PRIORITY = Number("25");
2625
+ var TASK_CONTEXT_PRIORITY = Number("27");
1593
2626
  function createSection(id, title, priority, content, source) {
1594
2627
  return { id, title, priority, content, source };
1595
2628
  }
@@ -1640,6 +2673,26 @@ function createClaudeMdSection(claudeMd) {
1640
2673
  if (claudeMd.trim().length === 0) return void 0;
1641
2674
  return createSection("project-claude-md", "Project Notes", 20, claudeMd, "project-instructions");
1642
2675
  }
2676
+ function createProjectMemorySection(memoryMd) {
2677
+ if (memoryMd === void 0 || memoryMd.trim().length === 0) return void 0;
2678
+ return createSection(
2679
+ "project-memory",
2680
+ "Project Memory",
2681
+ PROJECT_MEMORY_PRIORITY,
2682
+ memoryMd,
2683
+ "project-instructions"
2684
+ );
2685
+ }
2686
+ function createTaskContextSection(taskContext) {
2687
+ if (taskContext === void 0 || taskContext.trim().length === 0) return void 0;
2688
+ return createSection(
2689
+ "active-task-context",
2690
+ "Active Task Context",
2691
+ TASK_CONTEXT_PRIORITY,
2692
+ taskContext,
2693
+ "project-instructions"
2694
+ );
2695
+ }
1643
2696
  function createToolDescriptionSection(descriptions) {
1644
2697
  if (descriptions.length === 0) return void 0;
1645
2698
  return createSection(
@@ -1712,6 +2765,8 @@ function buildSystemPrompt(params) {
1712
2765
  const sections = [];
1713
2766
  appendOptionalSection(sections, createAgentsMdSection(params.agentsMd));
1714
2767
  appendOptionalSection(sections, createClaudeMdSection(params.claudeMd));
2768
+ appendOptionalSection(sections, createProjectMemorySection(params.memoryMd));
2769
+ appendOptionalSection(sections, createTaskContextSection(params.taskContext));
1715
2770
  appendOptionalSection(sections, createWorkingDirectorySection(params.cwd));
1716
2771
  sections.push(createProjectSection(params.projectInfo));
1717
2772
  appendOptionalSection(sections, createResponseLanguageSection(params.language));
@@ -1755,18 +2810,18 @@ function createDefaultTools() {
1755
2810
  }
1756
2811
 
1757
2812
  // src/tools/background-process-tool.ts
1758
- import { z as z2 } from "zod";
2813
+ import { z as z3 } from "zod";
1759
2814
  import { createZodFunctionTool as createZodFunctionTool2 } from "@robota-sdk/agent-tools";
1760
2815
  var DEFAULT_PROCESS_TIMEOUT_MS = 12e4;
1761
2816
  function asZodSchema2(schema) {
1762
2817
  return schema;
1763
2818
  }
1764
- var BackgroundProcessSchema = z2.object({
1765
- command: z2.string().describe("The shell command to start in the background"),
1766
- timeout: z2.number().optional().describe("Optional timeout in milliseconds. Default is 120000."),
1767
- workingDirectory: z2.string().optional().describe("Working directory for the command. Defaults to the current project directory."),
1768
- stdin: z2.string().optional().describe("Optional stdin to write after the process starts."),
1769
- outputLimitBytes: z2.number().optional().describe("Maximum captured output bytes kept in the task result.")
2819
+ var BackgroundProcessSchema = z3.object({
2820
+ command: z3.string().describe("The shell command to start in the background"),
2821
+ timeout: z3.number().optional().describe("Optional timeout in milliseconds. Default is 120000."),
2822
+ workingDirectory: z3.string().optional().describe("Working directory for the command. Defaults to the current project directory."),
2823
+ stdin: z3.string().optional().describe("Optional stdin to write after the process starts."),
2824
+ outputLimitBytes: z3.number().optional().describe("Maximum captured output bytes kept in the task result.")
1770
2825
  });
1771
2826
  function stringifyStarted(taskId, status, command) {
1772
2827
  return JSON.stringify({
@@ -1815,11 +2870,11 @@ function createBackgroundProcessTool(deps) {
1815
2870
  }
1816
2871
 
1817
2872
  // src/tools/command-execution-tool.ts
1818
- import { z as z3 } from "zod";
2873
+ import { z as z4 } from "zod";
1819
2874
  import { createZodFunctionTool as createZodFunctionTool3 } from "@robota-sdk/agent-tools";
1820
- var CommandExecutionSchema = z3.object({
1821
- command: z3.string().describe("Command name to execute, with or without a leading slash"),
1822
- args: z3.string().optional().describe("Arguments to pass to the command")
2875
+ var CommandExecutionSchema = z4.object({
2876
+ command: z4.string().describe("Command name to execute, with or without a leading slash"),
2877
+ args: z4.string().optional().describe("Arguments to pass to the command")
1823
2878
  });
1824
2879
  function asZodSchema3(schema) {
1825
2880
  return schema;
@@ -1862,12 +2917,55 @@ function createCommandExecutionTool(deps) {
1862
2917
  );
1863
2918
  }
1864
2919
 
2920
+ // src/checkpoints/edit-checkpoint-tools.ts
2921
+ var CHECKPOINTED_TOOL_NAMES = /* @__PURE__ */ new Set(["Write", "Edit"]);
2922
+ function wrapEditCheckpointTools(tools, recorder) {
2923
+ return tools.map(
2924
+ (tool) => CHECKPOINTED_TOOL_NAMES.has(tool.getName()) ? new EditCheckpointToolWrapper(tool, recorder) : tool
2925
+ );
2926
+ }
2927
+ var EditCheckpointToolWrapper = class {
2928
+ constructor(delegate, recorder) {
2929
+ this.delegate = delegate;
2930
+ this.recorder = recorder;
2931
+ this.schema = delegate.schema;
2932
+ }
2933
+ schema;
2934
+ setEventService(eventService) {
2935
+ this.delegate.setEventService(eventService);
2936
+ }
2937
+ async execute(parameters, context) {
2938
+ const filePath = extractFilePath(parameters);
2939
+ if (filePath) {
2940
+ await this.recorder.captureFile(filePath);
2941
+ }
2942
+ return this.delegate.execute(parameters, context);
2943
+ }
2944
+ validate(parameters) {
2945
+ return this.delegate.validate(parameters);
2946
+ }
2947
+ validateParameters(parameters) {
2948
+ return this.delegate.validateParameters(parameters);
2949
+ }
2950
+ getDescription() {
2951
+ return this.delegate.getDescription();
2952
+ }
2953
+ getName() {
2954
+ return this.delegate.getName();
2955
+ }
2956
+ };
2957
+ function extractFilePath(parameters) {
2958
+ if (!parameters || typeof parameters !== "object") return void 0;
2959
+ const value = parameters.filePath;
2960
+ return typeof value === "string" && value.length > 0 ? value : void 0;
2961
+ }
2962
+
1865
2963
  // src/assembly/create-session.ts
1866
2964
  import { BackgroundTaskManager as BackgroundTaskManager2, SubagentManager as SubagentManager2 } from "@robota-sdk/agent-runtime";
1867
2965
 
1868
2966
  // src/agents/agent-definition-loader.ts
1869
- import { readdirSync as readdirSync2, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
1870
- import { join as join2, basename as basename2 } from "path";
2967
+ import { readdirSync as readdirSync3, readFileSync as readFileSync6, existsSync as existsSync5 } from "fs";
2968
+ import { join as join5, basename as basename3 } from "path";
1871
2969
  import { homedir as homedir2 } from "os";
1872
2970
  var LIST_KEYS2 = /* @__PURE__ */ new Set(["tools", "disallowedTools"]);
1873
2971
  var NUMBER_KEYS = /* @__PURE__ */ new Set(["maxTurns"]);
@@ -1912,20 +3010,20 @@ function parseFrontmatter2(content) {
1912
3010
  };
1913
3011
  }
1914
3012
  function scanAgentsDir(dir) {
1915
- if (!existsSync2(dir)) return [];
3013
+ if (!existsSync5(dir)) return [];
1916
3014
  const agents = [];
1917
3015
  let entries;
1918
3016
  try {
1919
- entries = readdirSync2(dir, { withFileTypes: true });
3017
+ entries = readdirSync3(dir, { withFileTypes: true });
1920
3018
  } catch {
1921
3019
  return [];
1922
3020
  }
1923
3021
  for (const entry of entries) {
1924
3022
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
1925
- const filePath = join2(dir, entry.name);
1926
- const content = readFileSync2(filePath, "utf-8");
3023
+ const filePath = join5(dir, entry.name);
3024
+ const content = readFileSync6(filePath, "utf-8");
1927
3025
  const { frontmatter, body } = parseFrontmatter2(content);
1928
- const fallbackName = basename2(entry.name, ".md");
3026
+ const fallbackName = basename3(entry.name, ".md");
1929
3027
  const agent = {
1930
3028
  name: frontmatter?.name ?? fallbackName,
1931
3029
  description: frontmatter?.description ?? "",
@@ -1950,11 +3048,11 @@ var AgentDefinitionLoader = class {
1950
3048
  /** Load all agent definitions, merged with built-in agents. Custom overrides built-in on name collision. */
1951
3049
  loadAll() {
1952
3050
  const sources = [
1953
- scanAgentsDir(join2(this.cwd, ".robota", "agents")),
1954
- scanAgentsDir(join2(this.cwd, ".agents", "agents")),
1955
- scanAgentsDir(join2(this.cwd, ".claude", "agents")),
1956
- scanAgentsDir(join2(this.home, ".robota", "agents")),
1957
- scanAgentsDir(join2(this.home, ".claude", "agents"))
3051
+ scanAgentsDir(join5(this.cwd, ".robota", "agents")),
3052
+ scanAgentsDir(join5(this.cwd, ".agents", "agents")),
3053
+ scanAgentsDir(join5(this.cwd, ".claude", "agents")),
3054
+ scanAgentsDir(join5(this.home, ".robota", "agents")),
3055
+ scanAgentsDir(join5(this.home, ".claude", "agents"))
1958
3056
  ];
1959
3057
  const seen = /* @__PURE__ */ new Set();
1960
3058
  const customAgents = [];
@@ -2032,7 +3130,7 @@ function createSession(options) {
2032
3130
  const provider = options.provider;
2033
3131
  const cwd = options.cwd ?? process.cwd();
2034
3132
  const sessionId = options.sessionId ?? createSessionId();
2035
- const defaultTools = createDefaultTools();
3133
+ const defaultTools = options.editCheckpointRecorder ? wrapEditCheckpointTools(createDefaultTools(), options.editCheckpointRecorder) : createDefaultTools();
2036
3134
  const tools = [...defaultTools, ...options.additionalTools ?? []];
2037
3135
  if (options.modelCommandExecutor && options.isModelCommandInvocable) {
2038
3136
  tools.push(
@@ -2133,6 +3231,8 @@ function createSession(options) {
2133
3231
  const systemMessage = buildPrompt({
2134
3232
  agentsMd: options.context.agentsMd,
2135
3233
  claudeMd: options.context.claudeMd,
3234
+ memoryMd: options.context.memoryMd,
3235
+ taskContext: options.context.taskContext,
2136
3236
  toolDescriptions: options.toolDescriptions ?? (backgroundProcessToolDeps ? [
2137
3237
  ...defaultToolDescriptions,
2138
3238
  "BackgroundProcess \u2014 start long-running shell commands as managed background tasks"
@@ -2186,6 +3286,7 @@ ${options.appendSystemPrompt}` : systemMessage;
2186
3286
  sessionId,
2187
3287
  permissionHandler: options.permissionHandler,
2188
3288
  onTextDelta: options.onTextDelta,
3289
+ onContextUpdate: options.onContextUpdate,
2189
3290
  onToolExecution: options.onToolExecution,
2190
3291
  promptForApproval: options.promptForApproval,
2191
3292
  onCompact: options.onCompact,
@@ -2202,342 +3303,237 @@ ${options.appendSystemPrompt}` : systemMessage;
2202
3303
  function createSessionId() {
2203
3304
  return `session_${Date.now()}_${Math.random().toString(ID_RADIX).substr(2, ID_RANDOM_LENGTH)}`;
2204
3305
  }
2205
- function logBackgroundTaskEvent(logger, sessionId, event) {
2206
- logger.log(sessionId, "background_task_event", {
2207
- backgroundEventType: event.type,
2208
- backgroundEvent: event
2209
- });
2210
- }
2211
-
2212
- // src/assembly/subagent-logger.ts
2213
- import { mkdirSync } from "fs";
2214
- import { join as join3 } from "path";
2215
- import { FileSessionLogger } from "@robota-sdk/agent-sessions";
2216
- function createSubagentLogger(parentSessionId, _agentId, baseLogsDir) {
2217
- const subagentDir = join3(baseLogsDir, parentSessionId, "subagents");
2218
- mkdirSync(subagentDir, { recursive: true });
2219
- return new FileSessionLogger(subagentDir);
2220
- }
2221
- function resolveSubagentLogDir(parentSessionId, baseLogsDir) {
2222
- return join3(baseLogsDir, parentSessionId, "subagents");
2223
- }
2224
-
2225
- // src/interactive/interactive-session-init.ts
2226
- import { FileSessionLogger as FileSessionLogger2 } from "@robota-sdk/agent-sessions";
2227
-
2228
- // src/paths.ts
2229
- import { join as join4 } from "path";
2230
- import { homedir as homedir3 } from "os";
2231
- function projectPaths(cwd) {
2232
- const base = join4(cwd, ".robota");
2233
- return {
2234
- settings: join4(base, "settings.json"),
2235
- settingsLocal: join4(base, "settings.local.json"),
2236
- logs: join4(base, "logs"),
2237
- sessions: join4(base, "sessions")
2238
- };
2239
- }
2240
- function userPaths() {
2241
- const base = join4(homedir3(), ".robota");
2242
- return {
2243
- settings: join4(base, "settings.json"),
2244
- sessions: join4(base, "sessions")
2245
- };
2246
- }
2247
-
2248
- // src/config/config-loader.ts
2249
- import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
2250
- import { join as join5 } from "path";
2251
-
2252
- // src/config/config-types.ts
2253
- import { z as z4 } from "zod";
2254
- var ProviderSchema = z4.object({
2255
- name: z4.string().optional(),
2256
- model: z4.string().optional(),
2257
- apiKey: z4.string().optional(),
2258
- baseURL: z4.string().optional(),
2259
- timeout: z4.number().optional()
2260
- });
2261
- var ProviderProfileSchema = z4.object({
2262
- type: z4.string().optional(),
2263
- model: z4.string().optional(),
2264
- apiKey: z4.string().optional(),
2265
- baseURL: z4.string().optional(),
2266
- timeout: z4.number().optional()
2267
- });
2268
- var PermissionsSchema = z4.object({
2269
- /** Patterns that are always approved without prompting */
2270
- allow: z4.array(z4.string()).optional(),
2271
- /** Patterns that are always denied */
2272
- deny: z4.array(z4.string()).optional()
2273
- });
2274
- var EnvSchema = z4.record(z4.string()).optional();
2275
- var CommandHookDefinitionSchema = z4.object({
2276
- type: z4.literal("command"),
2277
- command: z4.string(),
2278
- timeout: z4.number().optional()
2279
- });
2280
- var HttpHookDefinitionSchema = z4.object({
2281
- type: z4.literal("http"),
2282
- url: z4.string(),
2283
- headers: z4.record(z4.string()).optional(),
2284
- timeout: z4.number().optional()
2285
- });
2286
- var PromptHookDefinitionSchema = z4.object({
2287
- type: z4.literal("prompt"),
2288
- prompt: z4.string(),
2289
- model: z4.string().optional()
2290
- });
2291
- var AgentHookDefinitionSchema = z4.object({
2292
- type: z4.literal("agent"),
2293
- agent: z4.string(),
2294
- maxTurns: z4.number().optional(),
2295
- timeout: z4.number().optional()
2296
- });
2297
- var HookDefinitionSchema = z4.discriminatedUnion("type", [
2298
- CommandHookDefinitionSchema,
2299
- HttpHookDefinitionSchema,
2300
- PromptHookDefinitionSchema,
2301
- AgentHookDefinitionSchema
2302
- ]);
2303
- var HookGroupSchema = z4.object({
2304
- matcher: z4.string(),
2305
- hooks: z4.array(HookDefinitionSchema)
2306
- });
2307
- var HooksSchema = z4.object({
2308
- PreToolUse: z4.array(HookGroupSchema).optional(),
2309
- PostToolUse: z4.array(HookGroupSchema).optional(),
2310
- SessionStart: z4.array(HookGroupSchema).optional(),
2311
- SessionEnd: z4.array(HookGroupSchema).optional(),
2312
- Stop: z4.array(HookGroupSchema).optional(),
2313
- StopFailure: z4.array(HookGroupSchema).optional(),
2314
- PreCompact: z4.array(HookGroupSchema).optional(),
2315
- PostCompact: z4.array(HookGroupSchema).optional(),
2316
- UserPromptSubmit: z4.array(HookGroupSchema).optional(),
2317
- SubagentStart: z4.array(HookGroupSchema).optional(),
2318
- SubagentStop: z4.array(HookGroupSchema).optional(),
2319
- WorktreeCreate: z4.array(HookGroupSchema).optional(),
2320
- WorktreeRemove: z4.array(HookGroupSchema).optional()
2321
- }).optional();
2322
- var EnabledPluginsSchema = z4.record(z4.boolean()).optional();
2323
- var MarketplaceSourceSchema = z4.object({
2324
- source: z4.object({
2325
- type: z4.enum(["github", "git", "local", "url"]),
2326
- repo: z4.string().optional(),
2327
- url: z4.string().optional(),
2328
- path: z4.string().optional(),
2329
- ref: z4.string().optional()
2330
- })
2331
- });
2332
- var ExtraKnownMarketplacesSchema = z4.record(MarketplaceSourceSchema).optional().catch(void 0);
2333
- var SettingsSchema = z4.object({
2334
- /** Trust level used when no --permission-mode flag is given */
2335
- defaultTrustLevel: z4.enum(["safe", "moderate", "full"]).optional(),
2336
- /** Response language (e.g., "ko", "en", "ja"). Injected into system prompt. */
2337
- language: z4.string().optional(),
2338
- /** Active provider profile key from providers. */
2339
- currentProvider: z4.string().optional(),
2340
- /** Provider profiles keyed by user-facing profile name. */
2341
- providers: z4.record(ProviderProfileSchema).optional(),
2342
- /** Legacy single-provider settings. Prefer currentProvider + providers for new config. */
2343
- provider: ProviderSchema.optional(),
2344
- permissions: PermissionsSchema.optional(),
2345
- env: EnvSchema,
2346
- hooks: HooksSchema,
2347
- /** Plugin enablement map: plugin name -> enabled/disabled */
2348
- enabledPlugins: EnabledPluginsSchema,
2349
- /** Extra marketplace URLs for BundlePlugin discovery */
2350
- extraKnownMarketplaces: ExtraKnownMarketplacesSchema
2351
- });
2352
-
2353
- // src/config/config-loader.ts
2354
- function getHomeDir() {
2355
- return process.env.HOME ?? process.env.USERPROFILE ?? "/";
2356
- }
2357
- var DEFAULTS = {
2358
- defaultTrustLevel: "moderate",
2359
- provider: {
2360
- name: "anthropic",
2361
- model: "claude-opus-4-5",
2362
- apiKey: void 0
2363
- },
2364
- permissions: {
2365
- allow: [],
2366
- deny: []
2367
- },
2368
- env: {}
2369
- };
2370
- function readJsonFile(filePath) {
2371
- if (!existsSync3(filePath)) {
2372
- return void 0;
2373
- }
2374
- const raw = readFileSync3(filePath, "utf-8").trim();
2375
- if (raw.length === 0) {
2376
- return void 0;
2377
- }
2378
- try {
2379
- return JSON.parse(raw);
2380
- } catch {
2381
- return void 0;
2382
- }
2383
- }
2384
- function resolveEnvRef(value) {
2385
- const ENV_PREFIX = "$ENV:";
2386
- if (value.startsWith(ENV_PREFIX)) {
2387
- const varName = value.slice(ENV_PREFIX.length);
2388
- return process.env[varName] ?? value;
2389
- }
2390
- return value;
2391
- }
2392
- function resolveEnvRefs(settings) {
2393
- const provider = settings.provider?.apiKey !== void 0 ? {
2394
- ...settings.provider,
2395
- apiKey: resolveEnvRef(settings.provider.apiKey)
2396
- } : settings.provider;
2397
- if (settings.providers !== void 0) {
2398
- const providers = Object.fromEntries(
2399
- Object.entries(settings.providers).map(([name, profile]) => [
2400
- name,
2401
- {
2402
- ...profile,
2403
- ...profile.apiKey !== void 0 && { apiKey: resolveEnvRef(profile.apiKey) }
2404
- }
2405
- ])
2406
- );
2407
- return {
2408
- ...settings,
2409
- provider,
2410
- providers
2411
- };
2412
- }
3306
+ function logBackgroundTaskEvent(logger, sessionId, event) {
3307
+ logger.log(sessionId, "background_task_event", {
3308
+ backgroundEventType: event.type,
3309
+ backgroundEvent: event
3310
+ });
3311
+ }
3312
+
3313
+ // src/assembly/subagent-logger.ts
3314
+ import { mkdirSync as mkdirSync3 } from "fs";
3315
+ import { join as join6 } from "path";
3316
+ import { FileSessionLogger } from "@robota-sdk/agent-sessions";
3317
+ function createSubagentLogger(parentSessionId, _agentId, baseLogsDir) {
3318
+ const subagentDir = join6(baseLogsDir, parentSessionId, "subagents");
3319
+ mkdirSync3(subagentDir, { recursive: true });
3320
+ return new FileSessionLogger(subagentDir);
3321
+ }
3322
+ function resolveSubagentLogDir(parentSessionId, baseLogsDir) {
3323
+ return join6(baseLogsDir, parentSessionId, "subagents");
3324
+ }
3325
+
3326
+ // src/interactive/interactive-session-init.ts
3327
+ import { FileSessionLogger as FileSessionLogger2 } from "@robota-sdk/agent-sessions";
3328
+
3329
+ // src/paths.ts
3330
+ import { join as join7 } from "path";
3331
+ import { homedir as homedir3 } from "os";
3332
+ function projectPaths(cwd) {
3333
+ const base = join7(cwd, ".robota");
2413
3334
  return {
2414
- ...settings,
2415
- provider
3335
+ settings: join7(base, "settings.json"),
3336
+ settingsLocal: join7(base, "settings.local.json"),
3337
+ logs: join7(base, "logs"),
3338
+ sessions: join7(base, "sessions"),
3339
+ memory: join7(base, "memory"),
3340
+ checkpoints: join7(base, "checkpoints")
2416
3341
  };
2417
3342
  }
2418
- function mergeSettings(layers) {
2419
- return layers.reduce((merged, layer) => {
2420
- return {
2421
- ...merged,
2422
- ...layer,
2423
- provider: merged.provider !== void 0 || layer.provider !== void 0 ? { ...merged.provider, ...layer.provider } : void 0,
2424
- permissions: merged.permissions !== void 0 || layer.permissions !== void 0 ? {
2425
- allow: layer.permissions?.allow ?? merged.permissions?.allow,
2426
- deny: layer.permissions?.deny ?? merged.permissions?.deny
2427
- } : void 0,
2428
- env: {
2429
- ...merged.env ?? {},
2430
- ...layer.env ?? {}
2431
- },
2432
- providers: merged.providers !== void 0 || layer.providers !== void 0 ? mergeProviders(merged.providers, layer.providers) : void 0,
2433
- enabledPlugins: merged.enabledPlugins !== void 0 || layer.enabledPlugins !== void 0 ? { ...merged.enabledPlugins ?? {}, ...layer.enabledPlugins ?? {} } : void 0,
2434
- extraKnownMarketplaces: layer.extraKnownMarketplaces ?? merged.extraKnownMarketplaces
2435
- };
2436
- }, {});
3343
+ function userPaths() {
3344
+ const base = join7(homedir3(), ".robota");
3345
+ return {
3346
+ settings: join7(base, "settings.json"),
3347
+ sessions: join7(base, "sessions")
3348
+ };
2437
3349
  }
2438
- function mergeProviders(base, override) {
2439
- const result = { ...base ?? {} };
2440
- for (const [name, profile] of Object.entries(override ?? {})) {
2441
- result[name] = {
2442
- ...result[name],
2443
- ...profile
2444
- };
3350
+
3351
+ // src/context/context-loader.ts
3352
+ import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
3353
+ import { join as join9, dirname as dirname3, resolve as resolve2 } from "path";
3354
+
3355
+ // src/context/task-context.ts
3356
+ import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync7, statSync, writeFileSync as writeFileSync3 } from "fs";
3357
+ import { basename as basename4, dirname as dirname2, isAbsolute, join as join8, relative, resolve } from "path";
3358
+ var TASKS_DIR = join8(".agents", "tasks");
3359
+ var README_FILENAME = "README.md";
3360
+ var MARKDOWN_EXTENSION = ".md";
3361
+ var DEFAULT_MAX_TASKS = Number("3");
3362
+ var STATUS_PRIORITIES = {
3363
+ "in-progress": Number("1"),
3364
+ todo: Number("2"),
3365
+ blocked: Number("3"),
3366
+ unknown: Number("4"),
3367
+ completed: Number("5")
3368
+ };
3369
+ function normalizeStatus(value) {
3370
+ const normalized = value?.trim().toLowerCase();
3371
+ if (normalized === "todo" || normalized === "in-progress" || normalized === "blocked" || normalized === "completed") {
3372
+ return normalized;
3373
+ }
3374
+ return "unknown";
3375
+ }
3376
+ function extractTitle(content, taskPath) {
3377
+ const heading = content.split(/\r?\n/).find((line) => /^#\s+/.test(line));
3378
+ return heading?.replace(/^#\s+/, "").trim() || basename4(taskPath, MARKDOWN_EXTENSION);
3379
+ }
3380
+ function extractMetadata(content, key) {
3381
+ const matcher = new RegExp(`^- \\*\\*${key}\\*\\*:\\s*(.+)$`, "im");
3382
+ return matcher.exec(content)?.[1]?.trim();
3383
+ }
3384
+ function extractSection(content, title) {
3385
+ const lines = content.split(/\r?\n/);
3386
+ const heading = new RegExp(`^(#{2,6})\\s+${title}\\b`, "i");
3387
+ const startIndex = lines.findIndex((line) => heading.test(line));
3388
+ if (startIndex < 0) {
3389
+ return void 0;
2445
3390
  }
2446
- return result;
2447
- }
2448
- function resolveProvider(merged) {
2449
- if (merged.currentProvider !== void 0) {
2450
- const profile = merged.providers?.[merged.currentProvider];
2451
- if (profile === void 0) {
2452
- throw new Error(`currentProvider "${merged.currentProvider}" was not found in providers`);
3391
+ const collected = [];
3392
+ for (const line of lines.slice(startIndex + Number("1"))) {
3393
+ if (/^##\s+/.test(line)) {
3394
+ break;
2453
3395
  }
2454
- if (profile.type === void 0) {
2455
- throw new Error(`Provider profile "${merged.currentProvider}" is missing type`);
3396
+ collected.push(line);
3397
+ }
3398
+ const result = collected.join("\n").trim();
3399
+ return result.length > 0 ? result : void 0;
3400
+ }
3401
+ function extractOpenItems(content) {
3402
+ return content.split(/\r?\n/).map((line) => /^- \[ \]\s+(.+)$/.exec(line)?.[1]?.trim()).filter((item) => item !== void 0 && item.length > 0);
3403
+ }
3404
+ function taskSortScore(task, currentBranch) {
3405
+ if (currentBranch && task.branch === currentBranch) {
3406
+ return Number("0");
3407
+ }
3408
+ return STATUS_PRIORITIES[task.status];
3409
+ }
3410
+ function formatTask(task) {
3411
+ const lines = [`### ${task.title}`, `- **Path:** \`${task.relativePath}\``];
3412
+ lines.push(`- **Status:** ${task.status}`);
3413
+ if (task.branch) lines.push(`- **Branch:** ${task.branch}`);
3414
+ if (task.scope) lines.push(`- **Scope:** ${task.scope}`);
3415
+ if (task.objective) lines.push(`- **Objective:** ${task.objective}`);
3416
+ if (task.openItems.length > 0) {
3417
+ lines.push("- **Open items:**");
3418
+ lines.push(...task.openItems.map((item) => ` - ${item}`));
3419
+ }
3420
+ return lines.join("\n");
3421
+ }
3422
+ function formatDate(date) {
3423
+ return date.toISOString().slice(Number("0"), Number("10"));
3424
+ }
3425
+ function upsertStatusLine(content, status) {
3426
+ const lines = content.split(/\r?\n/);
3427
+ const statusLine = `- **Status**: ${status}`;
3428
+ const statusIndex = lines.findIndex((line) => /^- \*\*Status\*\*:\s*/.test(line));
3429
+ if (statusIndex >= Number("0")) {
3430
+ lines[statusIndex] = statusLine;
3431
+ return lines.join("\n");
3432
+ }
3433
+ const hasTopHeading = lines.length > Number("0") && /^#\s+/.test(lines[Number("0")]);
3434
+ if (hasTopHeading) {
3435
+ lines.splice(Number("1"), Number("0"), "", statusLine);
3436
+ } else {
3437
+ lines.unshift(statusLine, "");
3438
+ }
3439
+ return lines.join("\n");
3440
+ }
3441
+ function appendProgressEntry(content, now, progressMessage) {
3442
+ const entryLines = [`### ${formatDate(now)}`, `- ${progressMessage.trim()}`];
3443
+ const lines = content.replace(/\s+$/u, "").split(/\r?\n/);
3444
+ const progressIndex = lines.findIndex((line) => /^## Progress\s*$/.test(line));
3445
+ if (progressIndex < Number("0")) {
3446
+ return [...lines, "", "## Progress", "", ...entryLines, ""].join("\n");
3447
+ }
3448
+ const nextHeadingIndex = lines.findIndex(
3449
+ (line, index) => index > progressIndex && /^##\s+/.test(line)
3450
+ );
3451
+ if (nextHeadingIndex < Number("0")) {
3452
+ return [...lines, "", ...entryLines, ""].join("\n");
3453
+ }
3454
+ lines.splice(nextHeadingIndex, Number("0"), "", ...entryLines, "");
3455
+ return lines.join("\n");
3456
+ }
3457
+ function resolveGitDirectory(cwd) {
3458
+ let current = resolve(cwd);
3459
+ let reachedRoot = false;
3460
+ while (!reachedRoot) {
3461
+ const gitPath = join8(current, ".git");
3462
+ if (existsSync6(gitPath)) {
3463
+ const stats = statSync(gitPath);
3464
+ if (stats.isDirectory()) return gitPath;
3465
+ const content = readFileSync7(gitPath, "utf8").trim();
3466
+ const gitdir = content.match(/^gitdir:\s*(.+)$/)?.[1];
3467
+ if (gitdir) return isAbsolute(gitdir) ? gitdir : resolve(current, gitdir);
2456
3468
  }
2457
- return {
2458
- name: profile.type,
2459
- model: profile.model ?? DEFAULTS.provider.model,
2460
- apiKey: profile.apiKey ?? DEFAULTS.provider.apiKey,
2461
- ...profile.baseURL !== void 0 && { baseURL: profile.baseURL },
2462
- ...profile.timeout !== void 0 && { timeout: profile.timeout }
2463
- };
3469
+ const parent = dirname2(current);
3470
+ reachedRoot = parent === current;
3471
+ current = parent;
2464
3472
  }
2465
- return {
2466
- name: merged.provider?.name ?? DEFAULTS.provider.name,
2467
- model: merged.provider?.model ?? DEFAULTS.provider.model,
2468
- apiKey: merged.provider?.apiKey ?? DEFAULTS.provider.apiKey,
2469
- ...merged.provider?.baseURL !== void 0 && { baseURL: merged.provider.baseURL },
2470
- ...merged.provider?.timeout !== void 0 && { timeout: merged.provider.timeout }
2471
- };
3473
+ return void 0;
2472
3474
  }
2473
- function toResolvedConfig(merged) {
3475
+ function readCurrentGitBranch(cwd) {
3476
+ const gitDir = resolveGitDirectory(cwd);
3477
+ if (!gitDir) return void 0;
3478
+ const headPath = join8(gitDir, "HEAD");
3479
+ if (!existsSync6(headPath)) return void 0;
3480
+ const head = readFileSync7(headPath, "utf8").trim();
3481
+ const branch = head.match(/^ref:\s+refs\/heads\/(.+)$/)?.[1];
3482
+ return branch?.trim();
3483
+ }
3484
+ function discoverTaskFiles(cwd) {
3485
+ const tasksDir = join8(cwd, TASKS_DIR);
3486
+ if (!existsSync6(tasksDir)) {
3487
+ return [];
3488
+ }
3489
+ return readdirSync4(tasksDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).filter((name) => name !== README_FILENAME && name.endsWith(MARKDOWN_EXTENSION)).sort((a, b) => a.localeCompare(b)).map((name) => join8(tasksDir, name));
3490
+ }
3491
+ function parseTaskFile(taskPath, cwd) {
3492
+ const content = readFileSync7(taskPath, "utf8");
2474
3493
  return {
2475
- defaultTrustLevel: merged.defaultTrustLevel ?? DEFAULTS.defaultTrustLevel,
2476
- language: merged.language,
2477
- currentProvider: merged.currentProvider,
2478
- provider: resolveProvider(merged),
2479
- permissions: {
2480
- allow: merged.permissions?.allow ?? DEFAULTS.permissions.allow,
2481
- deny: merged.permissions?.deny ?? DEFAULTS.permissions.deny
2482
- },
2483
- env: merged.env ?? DEFAULTS.env,
2484
- hooks: merged.hooks ?? void 0,
2485
- enabledPlugins: merged.enabledPlugins ?? void 0,
2486
- extraKnownMarketplaces: merged.extraKnownMarketplaces ?? void 0
3494
+ path: taskPath,
3495
+ relativePath: relative(cwd, taskPath),
3496
+ title: extractTitle(content, taskPath),
3497
+ status: normalizeStatus(extractMetadata(content, "Status")),
3498
+ branch: extractMetadata(content, "Branch"),
3499
+ scope: extractMetadata(content, "Scope"),
3500
+ objective: extractSection(content, "Objective"),
3501
+ openItems: extractOpenItems(content)
2487
3502
  };
2488
3503
  }
2489
- function getSettingsPaths(cwd) {
2490
- const home = getHomeDir();
2491
- return [
2492
- join5(home, ".robota", "settings.json"),
2493
- // 1. user (lowest)
2494
- join5(home, ".claude", "settings.json"),
2495
- // 1b. user (Claude Code compat)
2496
- join5(cwd, ".robota", "settings.json"),
2497
- // 2. project
2498
- join5(cwd, ".robota", "settings.local.json"),
2499
- // 3. project-local
2500
- join5(cwd, ".claude", "settings.json"),
2501
- // 4. project, Claude Code compat
2502
- join5(cwd, ".claude", "settings.local.json")
2503
- // 5. project-local (highest)
2504
- ];
3504
+ function selectRelevantTasks(tasks, options = {}) {
3505
+ const maxTasks = options.maxTasks ?? DEFAULT_MAX_TASKS;
3506
+ return [...tasks].filter((task) => task.status !== "completed").sort(
3507
+ (left, right) => taskSortScore(left, options.currentBranch) - taskSortScore(right, options.currentBranch) || left.relativePath.localeCompare(right.relativePath)
3508
+ ).slice(Number("0"), maxTasks);
2505
3509
  }
2506
- async function loadConfig(cwd) {
2507
- const allPaths = getSettingsPaths(cwd);
2508
- const rawEntries = [];
2509
- for (const filePath of allPaths) {
2510
- const raw = readJsonFile(filePath);
2511
- if (raw !== void 0) {
2512
- rawEntries.push({ raw, path: filePath });
2513
- }
2514
- }
2515
- const parsedLayers = rawEntries.map(({ raw, path }) => {
2516
- const result = SettingsSchema.safeParse(raw);
2517
- if (!result.success) {
2518
- throw new Error(`Invalid settings in ${path}: ${result.error.message}`);
2519
- }
2520
- return resolveEnvRefs(result.data);
2521
- });
2522
- const merged = mergeSettings(parsedLayers);
2523
- return toResolvedConfig(merged);
3510
+ function formatTaskContext(tasks) {
3511
+ return tasks.map(formatTask).join("\n\n");
3512
+ }
3513
+ function loadTaskContext(cwd, options = {}) {
3514
+ const currentBranch = options.currentBranch ?? readCurrentGitBranch(cwd);
3515
+ const tasks = discoverTaskFiles(cwd).map((path) => parseTaskFile(path, cwd));
3516
+ return formatTaskContext(selectRelevantTasks(tasks, { ...options, currentBranch }));
3517
+ }
3518
+ function updateTaskFileStatus(taskPath, status, options = {}) {
3519
+ const updated = upsertStatusLine(readFileSync7(taskPath, "utf8"), status);
3520
+ const withProgress = options.progressMessage ? appendProgressEntry(updated, options.now ?? /* @__PURE__ */ new Date(), options.progressMessage) : updated;
3521
+ writeFileSync3(taskPath, withProgress, "utf8");
2524
3522
  }
2525
3523
 
2526
3524
  // src/context/context-loader.ts
2527
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
2528
- import { join as join6, dirname, resolve } from "path";
2529
3525
  var AGENTS_FILENAME = "AGENTS.md";
2530
3526
  var CLAUDE_FILENAME = "CLAUDE.md";
2531
3527
  function collectFilesWalkingUp(startDir, filename) {
2532
3528
  const found = [];
2533
- let current = resolve(startDir);
3529
+ let current = resolve2(startDir);
2534
3530
  let atRoot = false;
2535
3531
  while (!atRoot) {
2536
- const candidate = join6(current, filename);
2537
- if (existsSync4(candidate)) {
3532
+ const candidate = join9(current, filename);
3533
+ if (existsSync7(candidate)) {
2538
3534
  found.push(candidate);
2539
3535
  }
2540
- const parent = dirname(current);
3536
+ const parent = dirname3(current);
2541
3537
  atRoot = parent === current;
2542
3538
  if (!atRoot) {
2543
3539
  current = parent;
@@ -2572,47 +3568,51 @@ function extractCompactInstructions(content) {
2572
3568
  async function loadContext(cwd) {
2573
3569
  const agentsPaths = collectFilesWalkingUp(cwd, AGENTS_FILENAME);
2574
3570
  const claudePaths = collectFilesWalkingUp(cwd, CLAUDE_FILENAME);
2575
- const agentsMd = agentsPaths.map((p) => readFileSync4(p, "utf-8")).join("\n\n");
2576
- const claudeMd = claudePaths.map((p) => readFileSync4(p, "utf-8")).join("\n\n");
3571
+ const agentsMd = agentsPaths.map((p) => readFileSync8(p, "utf-8")).join("\n\n");
3572
+ const claudeMd = claudePaths.map((p) => readFileSync8(p, "utf-8")).join("\n\n");
2577
3573
  const compactInstructions = extractCompactInstructions(claudeMd);
2578
- return { agentsMd, claudeMd, compactInstructions };
3574
+ const startupMemory = new ProjectMemoryStore(cwd).loadStartupMemory();
3575
+ const memoryMd = startupMemory.content || void 0;
3576
+ const loadedTaskContext = loadTaskContext(cwd);
3577
+ const taskContext = loadedTaskContext.trim().length > 0 ? loadedTaskContext : void 0;
3578
+ return { agentsMd, claudeMd, memoryMd, taskContext, compactInstructions };
2579
3579
  }
2580
3580
 
2581
3581
  // src/context/project-detector.ts
2582
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
2583
- import { join as join7 } from "path";
3582
+ import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
3583
+ import { join as join10 } from "path";
2584
3584
  function tryReadJson(filePath) {
2585
- if (!existsSync5(filePath)) return void 0;
3585
+ if (!existsSync8(filePath)) return void 0;
2586
3586
  try {
2587
- return JSON.parse(readFileSync5(filePath, "utf-8"));
3587
+ return JSON.parse(readFileSync9(filePath, "utf-8"));
2588
3588
  } catch {
2589
3589
  return void 0;
2590
3590
  }
2591
3591
  }
2592
3592
  function detectPackageManager(cwd) {
2593
- if (existsSync5(join7(cwd, "pnpm-workspace.yaml")) || existsSync5(join7(cwd, "pnpm-lock.yaml"))) {
3593
+ if (existsSync8(join10(cwd, "pnpm-workspace.yaml")) || existsSync8(join10(cwd, "pnpm-lock.yaml"))) {
2594
3594
  return "pnpm";
2595
3595
  }
2596
- if (existsSync5(join7(cwd, "yarn.lock"))) {
3596
+ if (existsSync8(join10(cwd, "yarn.lock"))) {
2597
3597
  return "yarn";
2598
3598
  }
2599
- if (existsSync5(join7(cwd, "bun.lockb"))) {
3599
+ if (existsSync8(join10(cwd, "bun.lockb"))) {
2600
3600
  return "bun";
2601
3601
  }
2602
- if (existsSync5(join7(cwd, "package-lock.json"))) {
3602
+ if (existsSync8(join10(cwd, "package-lock.json"))) {
2603
3603
  return "npm";
2604
3604
  }
2605
3605
  return void 0;
2606
3606
  }
2607
3607
  async function detectProject(cwd) {
2608
- const pkgJsonPath = join7(cwd, "package.json");
2609
- const tsconfigPath = join7(cwd, "tsconfig.json");
2610
- const pyprojectPath = join7(cwd, "pyproject.toml");
2611
- const cargoPath = join7(cwd, "Cargo.toml");
2612
- const goModPath = join7(cwd, "go.mod");
2613
- if (existsSync5(pkgJsonPath)) {
3608
+ const pkgJsonPath = join10(cwd, "package.json");
3609
+ const tsconfigPath = join10(cwd, "tsconfig.json");
3610
+ const pyprojectPath = join10(cwd, "pyproject.toml");
3611
+ const cargoPath = join10(cwd, "Cargo.toml");
3612
+ const goModPath = join10(cwd, "go.mod");
3613
+ if (existsSync8(pkgJsonPath)) {
2614
3614
  const pkgJson = tryReadJson(pkgJsonPath);
2615
- const language = existsSync5(tsconfigPath) ? "typescript" : "javascript";
3615
+ const language = existsSync8(tsconfigPath) ? "typescript" : "javascript";
2616
3616
  const packageManager = detectPackageManager(cwd);
2617
3617
  return {
2618
3618
  type: "node",
@@ -2621,19 +3621,19 @@ async function detectProject(cwd) {
2621
3621
  language
2622
3622
  };
2623
3623
  }
2624
- if (existsSync5(pyprojectPath) || existsSync5(join7(cwd, "setup.py"))) {
3624
+ if (existsSync8(pyprojectPath) || existsSync8(join10(cwd, "setup.py"))) {
2625
3625
  return {
2626
3626
  type: "python",
2627
3627
  language: "python"
2628
3628
  };
2629
3629
  }
2630
- if (existsSync5(cargoPath)) {
3630
+ if (existsSync8(cargoPath)) {
2631
3631
  return {
2632
3632
  type: "rust",
2633
3633
  language: "rust"
2634
3634
  };
2635
3635
  }
2636
- if (existsSync5(goModPath)) {
3636
+ if (existsSync8(goModPath)) {
2637
3637
  return {
2638
3638
  type: "go",
2639
3639
  language: "go"
@@ -2646,8 +3646,8 @@ async function detectProject(cwd) {
2646
3646
  }
2647
3647
 
2648
3648
  // src/plugins/plugin-settings-store.ts
2649
- import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync, mkdirSync as mkdirSync2 } from "fs";
2650
- import { dirname as dirname2 } from "path";
3649
+ import { existsSync as existsSync9, readFileSync as readFileSync10, writeFileSync as writeFileSync4, mkdirSync as mkdirSync4 } from "fs";
3650
+ import { dirname as dirname4 } from "path";
2651
3651
  var PluginSettingsStore = class {
2652
3652
  settingsPath;
2653
3653
  constructor(settingsPath) {
@@ -2655,11 +3655,11 @@ var PluginSettingsStore = class {
2655
3655
  }
2656
3656
  /** Read the full settings file from disk. */
2657
3657
  readAll() {
2658
- if (!existsSync6(this.settingsPath)) {
3658
+ if (!existsSync9(this.settingsPath)) {
2659
3659
  return {};
2660
3660
  }
2661
3661
  try {
2662
- const raw = readFileSync6(this.settingsPath, "utf-8");
3662
+ const raw = readFileSync10(this.settingsPath, "utf-8");
2663
3663
  const data = JSON.parse(raw);
2664
3664
  if (typeof data === "object" && data !== null) {
2665
3665
  return data;
@@ -2671,11 +3671,11 @@ var PluginSettingsStore = class {
2671
3671
  }
2672
3672
  /** Write the full settings file to disk. */
2673
3673
  writeAll(settings) {
2674
- const dir = dirname2(this.settingsPath);
2675
- if (!existsSync6(dir)) {
2676
- mkdirSync2(dir, { recursive: true });
3674
+ const dir = dirname4(this.settingsPath);
3675
+ if (!existsSync9(dir)) {
3676
+ mkdirSync4(dir, { recursive: true });
2677
3677
  }
2678
- writeFileSync(this.settingsPath, JSON.stringify(settings, null, 2), "utf-8");
3678
+ writeFileSync4(this.settingsPath, JSON.stringify(settings, null, 2), "utf-8");
2679
3679
  }
2680
3680
  // --- enabledPlugins ---
2681
3681
  /** Get the enabledPlugins map. */
@@ -2747,11 +3747,11 @@ var PluginSettingsStore = class {
2747
3747
  };
2748
3748
 
2749
3749
  // src/plugins/bundle-plugin-loader.ts
2750
- import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync7 } from "fs";
2751
- import { join as join8 } from "path";
3750
+ import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync11 } from "fs";
3751
+ import { join as join11 } from "path";
2752
3752
 
2753
3753
  // src/plugins/bundle-plugin-utils.ts
2754
- import { existsSync as existsSync7, readdirSync as readdirSync3 } from "fs";
3754
+ import { existsSync as existsSync10, readdirSync as readdirSync5 } from "fs";
2755
3755
  function parseSkillFrontmatter(raw) {
2756
3756
  const trimmed = raw.trimStart();
2757
3757
  if (!trimmed.startsWith("---")) {
@@ -2800,9 +3800,9 @@ function validateManifest(data) {
2800
3800
  };
2801
3801
  }
2802
3802
  function getSortedSubdirs(dirPath) {
2803
- if (!existsSync7(dirPath)) return [];
3803
+ if (!existsSync10(dirPath)) return [];
2804
3804
  try {
2805
- const entries = readdirSync3(dirPath, { withFileTypes: true });
3805
+ const entries = readdirSync5(dirPath, { withFileTypes: true });
2806
3806
  return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
2807
3807
  } catch {
2808
3808
  return [];
@@ -2832,23 +3832,23 @@ var BundlePluginLoader = class {
2832
3832
  * For each marketplace/plugin pair, the latest version (lexicographically last) is loaded.
2833
3833
  */
2834
3834
  discoverAndLoad() {
2835
- const cacheDir = join8(this.pluginsDir, "cache");
2836
- if (!existsSync8(cacheDir)) {
3835
+ const cacheDir = join11(this.pluginsDir, "cache");
3836
+ if (!existsSync11(cacheDir)) {
2837
3837
  return [];
2838
3838
  }
2839
3839
  const results = [];
2840
3840
  const marketplaces = getSortedSubdirs(cacheDir);
2841
3841
  for (const marketplace of marketplaces) {
2842
- const marketplaceDir = join8(cacheDir, marketplace);
3842
+ const marketplaceDir = join11(cacheDir, marketplace);
2843
3843
  const plugins = getSortedSubdirs(marketplaceDir);
2844
3844
  for (const pluginName of plugins) {
2845
- const pluginDir = join8(marketplaceDir, pluginName);
3845
+ const pluginDir = join11(marketplaceDir, pluginName);
2846
3846
  const versions = getSortedSubdirs(pluginDir);
2847
3847
  if (versions.length === 0) continue;
2848
3848
  const latestVersion = versions[versions.length - 1];
2849
- const versionDir = join8(pluginDir, latestVersion);
2850
- const manifestPath = join8(versionDir, ".claude-plugin", "plugin.json");
2851
- if (!existsSync8(manifestPath)) continue;
3849
+ const versionDir = join11(pluginDir, latestVersion);
3850
+ const manifestPath = join11(versionDir, ".claude-plugin", "plugin.json");
3851
+ if (!existsSync11(manifestPath)) continue;
2852
3852
  const manifest = this.readManifest(manifestPath);
2853
3853
  if (!manifest) continue;
2854
3854
  const pluginId = `${manifest.name}@${marketplace}`;
@@ -2862,7 +3862,7 @@ var BundlePluginLoader = class {
2862
3862
  /** Read and validate a plugin.json manifest. Returns null on failure. */
2863
3863
  readManifest(path) {
2864
3864
  try {
2865
- const raw = readFileSync7(path, "utf-8");
3865
+ const raw = readFileSync11(path, "utf-8");
2866
3866
  const data = JSON.parse(raw);
2867
3867
  return validateManifest(data);
2868
3868
  } catch {
@@ -2897,15 +3897,15 @@ var BundlePluginLoader = class {
2897
3897
  }
2898
3898
  /** Load skills from the plugin's skills/ directory. */
2899
3899
  loadSkills(pluginDir, pluginName) {
2900
- const skillsDir = join8(pluginDir, "skills");
2901
- if (!existsSync8(skillsDir)) return [];
2902
- const entries = readdirSync4(skillsDir, { withFileTypes: true });
3900
+ const skillsDir = join11(pluginDir, "skills");
3901
+ if (!existsSync11(skillsDir)) return [];
3902
+ const entries = readdirSync6(skillsDir, { withFileTypes: true });
2903
3903
  const skills = [];
2904
3904
  for (const entry of entries) {
2905
3905
  if (!entry.isDirectory()) continue;
2906
- const skillFile = join8(skillsDir, entry.name, "SKILL.md");
2907
- if (!existsSync8(skillFile)) continue;
2908
- const raw = readFileSync7(skillFile, "utf-8");
3906
+ const skillFile = join11(skillsDir, entry.name, "SKILL.md");
3907
+ if (!existsSync11(skillFile)) continue;
3908
+ const raw = readFileSync11(skillFile, "utf-8");
2909
3909
  const { metadata, content } = parseSkillFrontmatter(raw);
2910
3910
  const description = typeof metadata.description === "string" ? metadata.description : "";
2911
3911
  const skill = {
@@ -2920,13 +3920,13 @@ var BundlePluginLoader = class {
2920
3920
  }
2921
3921
  /** Load commands from the plugin's commands/ directory (flat .md files). */
2922
3922
  loadCommands(pluginDir, pluginName) {
2923
- const commandsDir = join8(pluginDir, "commands");
2924
- if (!existsSync8(commandsDir)) return [];
2925
- const entries = readdirSync4(commandsDir, { withFileTypes: true });
3923
+ const commandsDir = join11(pluginDir, "commands");
3924
+ if (!existsSync11(commandsDir)) return [];
3925
+ const entries = readdirSync6(commandsDir, { withFileTypes: true });
2926
3926
  const commands = [];
2927
3927
  for (const entry of entries) {
2928
3928
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
2929
- const raw = readFileSync7(join8(commandsDir, entry.name), "utf-8");
3929
+ const raw = readFileSync11(join11(commandsDir, entry.name), "utf-8");
2930
3930
  const { metadata, content } = parseSkillFrontmatter(raw);
2931
3931
  const name = typeof metadata.name === "string" ? metadata.name : entry.name.replace(/\.md$/, "");
2932
3932
  const description = typeof metadata.description === "string" ? metadata.description : "";
@@ -2941,10 +3941,10 @@ var BundlePluginLoader = class {
2941
3941
  }
2942
3942
  /** Load hooks from hooks/hooks.json if present. */
2943
3943
  loadHooks(pluginDir) {
2944
- const hooksPath = join8(pluginDir, "hooks", "hooks.json");
2945
- if (!existsSync8(hooksPath)) return {};
3944
+ const hooksPath = join11(pluginDir, "hooks", "hooks.json");
3945
+ if (!existsSync11(hooksPath)) return {};
2946
3946
  try {
2947
- const raw = readFileSync7(hooksPath, "utf-8");
3947
+ const raw = readFileSync11(hooksPath, "utf-8");
2948
3948
  const data = JSON.parse(raw);
2949
3949
  if (typeof data === "object" && data !== null) {
2950
3950
  return data;
@@ -2956,12 +3956,12 @@ var BundlePluginLoader = class {
2956
3956
  }
2957
3957
  /** Load MCP server configuration if present. Checks `.mcp.json` at plugin root first. */
2958
3958
  loadMcpConfig(pluginDir) {
2959
- const primaryPath = join8(pluginDir, ".mcp.json");
2960
- const fallbackPath = join8(pluginDir, ".claude-plugin", "mcp.json");
2961
- const mcpPath = existsSync8(primaryPath) ? primaryPath : fallbackPath;
2962
- if (!existsSync8(mcpPath)) return void 0;
3959
+ const primaryPath = join11(pluginDir, ".mcp.json");
3960
+ const fallbackPath = join11(pluginDir, ".claude-plugin", "mcp.json");
3961
+ const mcpPath = existsSync11(primaryPath) ? primaryPath : fallbackPath;
3962
+ if (!existsSync11(mcpPath)) return void 0;
2963
3963
  try {
2964
- const raw = readFileSync7(mcpPath, "utf-8");
3964
+ const raw = readFileSync11(mcpPath, "utf-8");
2965
3965
  return JSON.parse(raw);
2966
3966
  } catch {
2967
3967
  return void 0;
@@ -2969,10 +3969,10 @@ var BundlePluginLoader = class {
2969
3969
  }
2970
3970
  /** Load agent definitions from agents/ directory if present. */
2971
3971
  loadAgents(pluginDir) {
2972
- const agentsDir = join8(pluginDir, "agents");
2973
- if (!existsSync8(agentsDir)) return [];
3972
+ const agentsDir = join11(pluginDir, "agents");
3973
+ if (!existsSync11(agentsDir)) return [];
2974
3974
  try {
2975
- const entries = readdirSync4(agentsDir, { withFileTypes: true });
3975
+ const entries = readdirSync6(agentsDir, { withFileTypes: true });
2976
3976
  return entries.filter((e) => e.isDirectory() || e.name.endsWith(".md")).map((e) => e.name.replace(/\.md$/, ""));
2977
3977
  } catch {
2978
3978
  return [];
@@ -2982,8 +3982,8 @@ var BundlePluginLoader = class {
2982
3982
 
2983
3983
  // src/plugins/bundle-plugin-installer.ts
2984
3984
  import { execSync as execSync2 } from "child_process";
2985
- import { cpSync, existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync8, rmSync, writeFileSync as writeFileSync2 } from "fs";
2986
- import { join as join9, dirname as dirname3 } from "path";
3985
+ import { cpSync, existsSync as existsSync12, mkdirSync as mkdirSync5, readFileSync as readFileSync12, rmSync, writeFileSync as writeFileSync5 } from "fs";
3986
+ import { join as join12, dirname as dirname5 } from "path";
2987
3987
  var GIT_CLONE_TIMEOUT_MS = 6e4;
2988
3988
  var BundlePluginInstaller = class {
2989
3989
  pluginsDir;
@@ -2994,8 +3994,8 @@ var BundlePluginInstaller = class {
2994
3994
  exec;
2995
3995
  constructor(options) {
2996
3996
  this.pluginsDir = options.pluginsDir;
2997
- this.cacheDir = join9(this.pluginsDir, "cache");
2998
- this.registryPath = join9(this.pluginsDir, "installed_plugins.json");
3997
+ this.cacheDir = join12(this.pluginsDir, "cache");
3998
+ this.registryPath = join12(this.pluginsDir, "installed_plugins.json");
2999
3999
  this.settingsStore = options.settingsStore;
3000
4000
  this.marketplaceClient = options.marketplaceClient;
3001
4001
  this.exec = options.exec ?? this.defaultExec;
@@ -3015,8 +4015,8 @@ var BundlePluginInstaller = class {
3015
4015
  throw new Error(`Plugin "${pluginName}" not found in marketplace "${marketplaceName}"`);
3016
4016
  }
3017
4017
  const version = this.resolveVersion(entry, marketplaceName);
3018
- const targetDir = join9(this.cacheDir, marketplaceName, pluginName, version);
3019
- if (existsSync9(targetDir)) {
4018
+ const targetDir = join12(this.cacheDir, marketplaceName, pluginName, version);
4019
+ if (existsSync12(targetDir)) {
3020
4020
  throw new Error(
3021
4021
  `Plugin "${pluginName}" version "${version}" is already installed from "${marketplaceName}"`
3022
4022
  );
@@ -3043,7 +4043,7 @@ var BundlePluginInstaller = class {
3043
4043
  if (!record) {
3044
4044
  throw new Error(`Plugin "${pluginId}" is not installed`);
3045
4045
  }
3046
- if (existsSync9(record.installPath)) {
4046
+ if (existsSync12(record.installPath)) {
3047
4047
  rmSync(record.installPath, { recursive: true, force: true });
3048
4048
  }
3049
4049
  delete registry[pluginId];
@@ -3090,13 +4090,13 @@ var BundlePluginInstaller = class {
3090
4090
  }
3091
4091
  /** Resolve the source and install the plugin. */
3092
4092
  resolveAndInstall(rawSource, marketplaceName, pluginName, targetDir) {
3093
- mkdirSync3(targetDir, { recursive: true });
4093
+ mkdirSync5(targetDir, { recursive: true });
3094
4094
  const source = this.normalizeSource(rawSource);
3095
4095
  try {
3096
4096
  if (typeof source === "string") {
3097
4097
  const marketplaceDir = this.marketplaceClient.getMarketplaceDir(marketplaceName);
3098
- const sourcePath = join9(marketplaceDir, source);
3099
- if (!existsSync9(sourcePath)) {
4098
+ const sourcePath = join12(marketplaceDir, source);
4099
+ if (!existsSync12(sourcePath)) {
3100
4100
  throw new Error(
3101
4101
  `Plugin source path "${source}" not found in marketplace "${marketplaceName}"`
3102
4102
  );
@@ -3113,7 +4113,7 @@ var BundlePluginInstaller = class {
3113
4113
  throw new Error(`Unknown source type: ${JSON.stringify(source)}`);
3114
4114
  }
3115
4115
  } catch (err) {
3116
- if (existsSync9(targetDir)) {
4116
+ if (existsSync12(targetDir)) {
3117
4117
  rmSync(targetDir, { recursive: true, force: true });
3118
4118
  }
3119
4119
  throw err;
@@ -3132,11 +4132,11 @@ var BundlePluginInstaller = class {
3132
4132
  }
3133
4133
  /** Read the installed_plugins.json registry. */
3134
4134
  readRegistry() {
3135
- if (!existsSync9(this.registryPath)) {
4135
+ if (!existsSync12(this.registryPath)) {
3136
4136
  return {};
3137
4137
  }
3138
4138
  try {
3139
- const raw = readFileSync8(this.registryPath, "utf-8");
4139
+ const raw = readFileSync12(this.registryPath, "utf-8");
3140
4140
  const data = JSON.parse(raw);
3141
4141
  if (typeof data === "object" && data !== null) {
3142
4142
  return data;
@@ -3148,11 +4148,11 @@ var BundlePluginInstaller = class {
3148
4148
  }
3149
4149
  /** Write the installed_plugins.json registry. */
3150
4150
  writeRegistry(registry) {
3151
- const dir = dirname3(this.registryPath);
3152
- if (!existsSync9(dir)) {
3153
- mkdirSync3(dir, { recursive: true });
4151
+ const dir = dirname5(this.registryPath);
4152
+ if (!existsSync12(dir)) {
4153
+ mkdirSync5(dir, { recursive: true });
3154
4154
  }
3155
- writeFileSync2(this.registryPath, JSON.stringify(registry, null, 2), "utf-8");
4155
+ writeFileSync5(this.registryPath, JSON.stringify(registry, null, 2), "utf-8");
3156
4156
  }
3157
4157
  /** Default exec implementation using child_process. */
3158
4158
  defaultExec(command, options) {
@@ -3162,18 +4162,18 @@ var BundlePluginInstaller = class {
3162
4162
 
3163
4163
  // src/plugins/marketplace-client.ts
3164
4164
  import { execSync as execSync3 } from "child_process";
3165
- import { cpSync as cpSync2, existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync10, renameSync, rmSync as rmSync3 } from "fs";
3166
- import { join as join11 } from "path";
4165
+ import { cpSync as cpSync2, existsSync as existsSync14, mkdirSync as mkdirSync7, readFileSync as readFileSync14, renameSync, rmSync as rmSync3 } from "fs";
4166
+ import { join as join14 } from "path";
3167
4167
 
3168
4168
  // src/plugins/marketplace-registry.ts
3169
- import { existsSync as existsSync10, mkdirSync as mkdirSync4, readFileSync as readFileSync9, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
3170
- import { join as join10, dirname as dirname4 } from "path";
4169
+ import { existsSync as existsSync13, mkdirSync as mkdirSync6, readFileSync as readFileSync13, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
4170
+ import { join as join13, dirname as dirname6 } from "path";
3171
4171
  function readRegistry(registryPath) {
3172
- if (!existsSync10(registryPath)) {
4172
+ if (!existsSync13(registryPath)) {
3173
4173
  return {};
3174
4174
  }
3175
4175
  try {
3176
- const raw = readFileSync9(registryPath, "utf-8");
4176
+ const raw = readFileSync13(registryPath, "utf-8");
3177
4177
  const data = JSON.parse(raw);
3178
4178
  if (typeof data === "object" && data !== null) {
3179
4179
  return data;
@@ -3184,18 +4184,18 @@ function readRegistry(registryPath) {
3184
4184
  }
3185
4185
  }
3186
4186
  function writeRegistry(registryPath, registry) {
3187
- const dir = dirname4(registryPath);
3188
- if (!existsSync10(dir)) {
3189
- mkdirSync4(dir, { recursive: true });
4187
+ const dir = dirname6(registryPath);
4188
+ if (!existsSync13(dir)) {
4189
+ mkdirSync6(dir, { recursive: true });
3190
4190
  }
3191
- writeFileSync3(registryPath, JSON.stringify(registry, null, 2), "utf-8");
4191
+ writeFileSync6(registryPath, JSON.stringify(registry, null, 2), "utf-8");
3192
4192
  }
3193
4193
  function removeInstalledPluginsForMarketplace(pluginsDir, marketplaceName) {
3194
- const installedPath = join10(pluginsDir, "installed_plugins.json");
3195
- if (!existsSync10(installedPath)) return;
4194
+ const installedPath = join13(pluginsDir, "installed_plugins.json");
4195
+ if (!existsSync13(installedPath)) return;
3196
4196
  let registry;
3197
4197
  try {
3198
- const raw = readFileSync9(installedPath, "utf-8");
4198
+ const raw = readFileSync13(installedPath, "utf-8");
3199
4199
  const data = JSON.parse(raw);
3200
4200
  if (typeof data !== "object" || data === null) return;
3201
4201
  registry = data;
@@ -3205,7 +4205,7 @@ function removeInstalledPluginsForMarketplace(pluginsDir, marketplaceName) {
3205
4205
  let changed = false;
3206
4206
  for (const [pluginId, record] of Object.entries(registry)) {
3207
4207
  if (record.marketplace === marketplaceName) {
3208
- if (record.installPath && existsSync10(record.installPath)) {
4208
+ if (record.installPath && existsSync13(record.installPath)) {
3209
4209
  rmSync2(record.installPath, { recursive: true, force: true });
3210
4210
  }
3211
4211
  delete registry[pluginId];
@@ -3213,11 +4213,11 @@ function removeInstalledPluginsForMarketplace(pluginsDir, marketplaceName) {
3213
4213
  }
3214
4214
  }
3215
4215
  if (changed) {
3216
- const dir = dirname4(installedPath);
3217
- if (!existsSync10(dir)) {
3218
- mkdirSync4(dir, { recursive: true });
4216
+ const dir = dirname6(installedPath);
4217
+ if (!existsSync13(dir)) {
4218
+ mkdirSync6(dir, { recursive: true });
3219
4219
  }
3220
- writeFileSync3(installedPath, JSON.stringify(registry, null, 2), "utf-8");
4220
+ writeFileSync6(installedPath, JSON.stringify(registry, null, 2), "utf-8");
3221
4221
  }
3222
4222
  }
3223
4223
 
@@ -3231,8 +4231,8 @@ var MarketplaceClient = class {
3231
4231
  constructor(options) {
3232
4232
  this.pluginsDir = options.pluginsDir;
3233
4233
  this.exec = options.exec ?? this.defaultExec;
3234
- this.marketplacesDir = join11(this.pluginsDir, "marketplaces");
3235
- this.registryPath = join11(this.pluginsDir, "known_marketplaces.json");
4234
+ this.marketplacesDir = join14(this.pluginsDir, "marketplaces");
4235
+ this.registryPath = join14(this.pluginsDir, "known_marketplaces.json");
3236
4236
  }
3237
4237
  /**
3238
4238
  * Add a marketplace by cloning its repository.
@@ -3245,10 +4245,10 @@ var MarketplaceClient = class {
3245
4245
  */
3246
4246
  addMarketplace(source) {
3247
4247
  const tempName = "temp-" + Date.now().toString(36);
3248
- const tempDir = join11(this.marketplacesDir, tempName);
3249
- mkdirSync5(this.marketplacesDir, { recursive: true });
4248
+ const tempDir = join14(this.marketplacesDir, tempName);
4249
+ mkdirSync7(this.marketplacesDir, { recursive: true });
3250
4250
  if (source.type === "local") {
3251
- if (!existsSync11(source.path)) {
4251
+ if (!existsSync14(source.path)) {
3252
4252
  throw new Error(`Local marketplace path does not exist: ${source.path}`);
3253
4253
  }
3254
4254
  cpSync2(source.path, tempDir, { recursive: true });
@@ -3262,8 +4262,8 @@ var MarketplaceClient = class {
3262
4262
  throw new Error(`Failed to clone marketplace: ${message}`);
3263
4263
  }
3264
4264
  }
3265
- const manifestPath = join11(tempDir, ".claude-plugin", "marketplace.json");
3266
- if (!existsSync11(manifestPath)) {
4265
+ const manifestPath = join14(tempDir, ".claude-plugin", "marketplace.json");
4266
+ if (!existsSync14(manifestPath)) {
3267
4267
  rmSync3(tempDir, { recursive: true, force: true });
3268
4268
  throw new Error(
3269
4269
  source.type === "local" ? "Local directory does not contain .claude-plugin/marketplace.json" : "Cloned repository does not contain .claude-plugin/marketplace.json"
@@ -3280,7 +4280,7 @@ var MarketplaceClient = class {
3280
4280
  rmSync3(tempDir, { recursive: true, force: true });
3281
4281
  throw new Error(`Marketplace "${name}" already exists`);
3282
4282
  }
3283
- const finalDir = join11(this.marketplacesDir, name);
4283
+ const finalDir = join14(this.marketplacesDir, name);
3284
4284
  renameSync(tempDir, finalDir);
3285
4285
  registry[name] = {
3286
4286
  source,
@@ -3302,7 +4302,7 @@ var MarketplaceClient = class {
3302
4302
  throw new Error(`Marketplace "${name}" not found`);
3303
4303
  }
3304
4304
  removeInstalledPluginsForMarketplace(this.pluginsDir, name);
3305
- if (existsSync11(entry.installLocation)) {
4305
+ if (existsSync14(entry.installLocation)) {
3306
4306
  rmSync3(entry.installLocation, { recursive: true, force: true });
3307
4307
  }
3308
4308
  delete registry[name];
@@ -3319,12 +4319,12 @@ var MarketplaceClient = class {
3319
4319
  if (!entry) {
3320
4320
  throw new Error(`Marketplace "${name}" not found`);
3321
4321
  }
3322
- if (!existsSync11(entry.installLocation)) {
4322
+ if (!existsSync14(entry.installLocation)) {
3323
4323
  throw new Error(`Marketplace directory for "${name}" does not exist`);
3324
4324
  }
3325
4325
  if (entry.source.type === "local") {
3326
4326
  const localSource = entry.source;
3327
- if (!existsSync11(localSource.path)) {
4327
+ if (!existsSync14(localSource.path)) {
3328
4328
  throw new Error(`Local marketplace path does not exist: ${localSource.path}`);
3329
4329
  }
3330
4330
  rmSync3(entry.installLocation, { recursive: true, force: true });
@@ -3357,8 +4357,8 @@ var MarketplaceClient = class {
3357
4357
  if (!entry) {
3358
4358
  throw new Error(`Marketplace "${marketplaceName}" not found`);
3359
4359
  }
3360
- const manifestPath = join11(entry.installLocation, ".claude-plugin", "marketplace.json");
3361
- if (!existsSync11(manifestPath)) {
4360
+ const manifestPath = join14(entry.installLocation, ".claude-plugin", "marketplace.json");
4361
+ if (!existsSync14(manifestPath)) {
3362
4362
  throw new Error(
3363
4363
  `Marketplace "${marketplaceName}" does not contain .claude-plugin/marketplace.json`
3364
4364
  );
@@ -3421,7 +4421,7 @@ var MarketplaceClient = class {
3421
4421
  }
3422
4422
  /** Read and parse a marketplace.json from a file path. */
3423
4423
  readManifestFromPath(path) {
3424
- const raw = readFileSync10(path, "utf-8");
4424
+ const raw = readFileSync14(path, "utf-8");
3425
4425
  const data = JSON.parse(raw);
3426
4426
  if (typeof data !== "object" || data === null) {
3427
4427
  throw new Error("Invalid marketplace manifest: not an object");
@@ -3439,9 +4439,9 @@ var MarketplaceClient = class {
3439
4439
  };
3440
4440
 
3441
4441
  // src/plugins/plugin-hooks-merger.ts
3442
- import { join as join12, dirname as dirname5 } from "path";
4442
+ import { join as join15, dirname as dirname7 } from "path";
3443
4443
  function buildPluginEnv(plugin) {
3444
- const dataDir = join12(dirname5(dirname5(plugin.pluginDir)), "data", plugin.manifest.name);
4444
+ const dataDir = join15(dirname7(dirname7(plugin.pluginDir)), "data", plugin.manifest.name);
3445
4445
  return {
3446
4446
  CLAUDE_PLUGIN_ROOT: plugin.pluginDir,
3447
4447
  CLAUDE_PLUGIN_PATH: plugin.pluginDir,
@@ -3504,15 +4504,15 @@ function mergeHooksIntoConfig(configHooks, pluginHooks) {
3504
4504
 
3505
4505
  // src/interactive/interactive-session-init.ts
3506
4506
  import { homedir as homedir4 } from "os";
3507
- import { join as join13 } from "path";
4507
+ import { join as join16 } from "path";
3508
4508
  async function createInteractiveSession(options) {
3509
4509
  const cwd = options.cwd;
3510
4510
  const [config, context, projectInfo] = await Promise.all([
3511
- loadConfig(cwd),
4511
+ options.config ? Promise.resolve(options.config) : loadConfig(cwd),
3512
4512
  options.bare ? Promise.resolve({ agentsMd: "", claudeMd: "" }) : loadContext(cwd),
3513
4513
  options.bare ? Promise.resolve({ type: "unknown", language: "unknown" }) : detectProject(cwd)
3514
4514
  ]);
3515
- const pluginsDir = join13(homedir4(), ".robota", "plugins");
4515
+ const pluginsDir = join16(homedir4(), ".robota", "plugins");
3516
4516
  const pluginLoader = new BundlePluginLoader(pluginsDir);
3517
4517
  let mergedConfig = config;
3518
4518
  if (!options.bare) {
@@ -3545,6 +4545,7 @@ async function createInteractiveSession(options) {
3545
4545
  permissionHandler: options.permissionHandler,
3546
4546
  provider: options.provider,
3547
4547
  onTextDelta: options.onTextDelta,
4548
+ onContextUpdate: options.onContextUpdate,
3548
4549
  onToolExecution: options.onToolExecution,
3549
4550
  sessionId,
3550
4551
  allowedTools: options.allowedTools,
@@ -3561,7 +4562,8 @@ async function createInteractiveSession(options) {
3561
4562
  ]
3562
4563
  } : {},
3563
4564
  modelCommandExecutor: options.modelCommandExecutor,
3564
- isModelCommandInvocable: options.isModelCommandInvocable
4565
+ isModelCommandInvocable: options.isModelCommandInvocable,
4566
+ editCheckpointRecorder: options.editCheckpointRecorder
3565
4567
  });
3566
4568
  }
3567
4569
  function injectSavedMessage(session, msg) {
@@ -3587,7 +4589,9 @@ function loadSessionRecord(sessionStore, resumeSessionId, forkSession, existingS
3587
4589
  backgroundTasks: [],
3588
4590
  backgroundTaskEvents: [],
3589
4591
  backgroundJobGroups: [],
3590
- backgroundJobGroupEvents: []
4592
+ backgroundJobGroupEvents: [],
4593
+ memoryEvents: [],
4594
+ usedMemoryReferences: []
3591
4595
  };
3592
4596
  }
3593
4597
  const history = record.history ?? [];
@@ -3595,6 +4599,8 @@ function loadSessionRecord(sessionStore, resumeSessionId, forkSession, existingS
3595
4599
  const restoredBackgroundTaskEvents = record.backgroundTaskEvents ?? [];
3596
4600
  const backgroundJobGroups = record.backgroundJobGroups ?? [];
3597
4601
  const backgroundJobGroupEvents = record.backgroundJobGroupEvents ?? [];
4602
+ const memoryEvents = record.memoryEvents ?? [];
4603
+ const usedMemoryReferences = record.usedMemoryReferences ?? [];
3598
4604
  const { backgroundTasks, backgroundTaskEvents } = reconcileRestoredBackgroundTasks(
3599
4605
  restoredBackgroundTasks,
3600
4606
  restoredBackgroundTaskEvents
@@ -3617,7 +4623,9 @@ function loadSessionRecord(sessionStore, resumeSessionId, forkSession, existingS
3617
4623
  backgroundTasks,
3618
4624
  backgroundTaskEvents,
3619
4625
  backgroundJobGroups,
3620
- backgroundJobGroupEvents
4626
+ backgroundJobGroupEvents,
4627
+ memoryEvents,
4628
+ usedMemoryReferences
3621
4629
  };
3622
4630
  }
3623
4631
  function reconcileRestoredBackgroundTasks(tasks, events) {
@@ -3650,6 +4658,189 @@ function isRestoredTerminalStatus(status) {
3650
4658
  return status === "completed" || status === "failed" || status === "cancelled";
3651
4659
  }
3652
4660
 
4661
+ // src/checkpoints/edit-checkpoint-store.ts
4662
+ import { access, copyFile, mkdir, rename, rm, writeFile } from "fs/promises";
4663
+ import { constants, readdirSync as readdirSync7, readFileSync as readFileSync15 } from "fs";
4664
+ import { dirname as dirname8, join as join17, relative as relative2, resolve as resolve3 } from "path";
4665
+ var MANIFEST_FILE = "manifest.json";
4666
+ var SNAPSHOT_DIR = "files";
4667
+ var ID_PAD = 4;
4668
+ var SNAPSHOT_PAD = 6;
4669
+ var EditCheckpointStore = class {
4670
+ cwd;
4671
+ rootDir;
4672
+ now;
4673
+ activeTurn = null;
4674
+ constructor(options) {
4675
+ this.cwd = resolve3(options.cwd);
4676
+ this.rootDir = projectPaths(this.cwd).checkpoints;
4677
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
4678
+ }
4679
+ async beginTurn(input) {
4680
+ if (this.activeTurn) {
4681
+ await this.finalizeTurn();
4682
+ }
4683
+ const nextSequence = this.nextSequence(input.sessionId);
4684
+ const id = `turn-${String(nextSequence).padStart(ID_PAD, "0")}`;
4685
+ const dir = join17(this.sessionDir(input.sessionId), id);
4686
+ await mkdir(join17(dir, SNAPSHOT_DIR), { recursive: true });
4687
+ const manifest = {
4688
+ version: 1,
4689
+ id,
4690
+ sessionId: input.sessionId,
4691
+ sequence: nextSequence,
4692
+ prompt: input.prompt,
4693
+ createdAt: this.now().toISOString(),
4694
+ fileCount: 0,
4695
+ files: []
4696
+ };
4697
+ this.activeTurn = {
4698
+ manifest,
4699
+ dir,
4700
+ capturedPaths: /* @__PURE__ */ new Set()
4701
+ };
4702
+ return toSummary(manifest);
4703
+ }
4704
+ async captureFile(filePath) {
4705
+ if (!this.activeTurn) return;
4706
+ const originalPath = resolve3(this.cwd, filePath);
4707
+ if (this.activeTurn.capturedPaths.has(originalPath)) return;
4708
+ if (isInside(this.rootDir, originalPath)) return;
4709
+ const record = await this.createFileRecord(originalPath, this.activeTurn);
4710
+ this.activeTurn.manifest.files.push(record);
4711
+ this.activeTurn.manifest.fileCount = this.activeTurn.manifest.files.length;
4712
+ this.activeTurn.capturedPaths.add(originalPath);
4713
+ }
4714
+ async finalizeTurn() {
4715
+ if (!this.activeTurn) return void 0;
4716
+ const active = this.activeTurn;
4717
+ this.activeTurn = null;
4718
+ await this.writeManifest(active.dir, active.manifest);
4719
+ return toSummary(active.manifest);
4720
+ }
4721
+ list(sessionId) {
4722
+ return this.loadManifests(sessionId).map(toSummary);
4723
+ }
4724
+ async restoreToCheckpoint(sessionId, checkpointId) {
4725
+ const manifests = this.loadManifests(sessionId);
4726
+ const target = manifests.find((manifest) => manifest.id === checkpointId);
4727
+ if (!target) {
4728
+ throw new Error(`Unknown edit checkpoint: ${checkpointId}`);
4729
+ }
4730
+ const later = manifests.filter((manifest) => manifest.sequence > target.sequence).sort((a, b) => b.sequence - a.sequence);
4731
+ let restoredFileCount = 0;
4732
+ for (const manifest of later) {
4733
+ for (const file of manifest.files) {
4734
+ await this.restoreFile(sessionId, manifest.id, file);
4735
+ restoredFileCount += 1;
4736
+ }
4737
+ }
4738
+ for (const manifest of later) {
4739
+ await rm(this.checkpointDir(sessionId, manifest.id), { recursive: true, force: true });
4740
+ }
4741
+ return {
4742
+ target: toSummary(target),
4743
+ restoredCheckpointCount: later.length,
4744
+ restoredFileCount,
4745
+ removedCheckpointCount: later.length
4746
+ };
4747
+ }
4748
+ async createFileRecord(originalPath, active) {
4749
+ const existed = await pathExists(originalPath);
4750
+ if (!existed) {
4751
+ return {
4752
+ originalPath,
4753
+ existed: false
4754
+ };
4755
+ }
4756
+ const snapshotFile = join17(
4757
+ SNAPSHOT_DIR,
4758
+ `${String(active.manifest.files.length + 1).padStart(SNAPSHOT_PAD, "0")}.content`
4759
+ );
4760
+ await copyFile(originalPath, join17(active.dir, snapshotFile));
4761
+ return {
4762
+ originalPath,
4763
+ existed: true,
4764
+ snapshotFile
4765
+ };
4766
+ }
4767
+ async restoreFile(sessionId, checkpointId, record) {
4768
+ if (!record.existed) {
4769
+ await rm(record.originalPath, { force: true });
4770
+ return;
4771
+ }
4772
+ if (!record.snapshotFile) {
4773
+ throw new Error(`Checkpoint file record is missing a snapshot: ${record.originalPath}`);
4774
+ }
4775
+ await mkdir(dirname8(record.originalPath), { recursive: true });
4776
+ await copyFile(
4777
+ join17(this.checkpointDir(sessionId, checkpointId), record.snapshotFile),
4778
+ record.originalPath
4779
+ );
4780
+ }
4781
+ loadManifests(sessionId) {
4782
+ const dir = this.sessionDir(sessionId);
4783
+ return readDirSyncSafe(dir).map((entry) => join17(dir, entry, MANIFEST_FILE)).map((manifestPath) => readJsonManifest(manifestPath)).filter((manifest) => manifest !== void 0).sort((a, b) => a.sequence - b.sequence);
4784
+ }
4785
+ nextSequence(sessionId) {
4786
+ const last = this.list(sessionId).at(-1);
4787
+ return (last?.sequence ?? 0) + 1;
4788
+ }
4789
+ async writeManifest(dir, manifest) {
4790
+ await mkdir(dir, { recursive: true });
4791
+ const path = join17(dir, MANIFEST_FILE);
4792
+ const tmp = `${path}.tmp`;
4793
+ await writeFile(tmp, JSON.stringify(manifest, null, 2), "utf8");
4794
+ await rename(tmp, path);
4795
+ }
4796
+ sessionDir(sessionId) {
4797
+ return join17(this.rootDir, safePathSegment(sessionId));
4798
+ }
4799
+ checkpointDir(sessionId, checkpointId) {
4800
+ return join17(this.sessionDir(sessionId), safePathSegment(checkpointId));
4801
+ }
4802
+ };
4803
+ function toSummary(manifest) {
4804
+ return {
4805
+ id: manifest.id,
4806
+ sessionId: manifest.sessionId,
4807
+ sequence: manifest.sequence,
4808
+ prompt: manifest.prompt,
4809
+ createdAt: manifest.createdAt,
4810
+ fileCount: manifest.fileCount
4811
+ };
4812
+ }
4813
+ function safePathSegment(value) {
4814
+ return value.replace(/[^a-zA-Z0-9._-]/g, "_");
4815
+ }
4816
+ function isInside(parent, child) {
4817
+ const rel = relative2(parent, child);
4818
+ return rel.length === 0 || !rel.startsWith("..") && !rel.startsWith("/");
4819
+ }
4820
+ async function pathExists(path) {
4821
+ try {
4822
+ await access(path, constants.F_OK);
4823
+ return true;
4824
+ } catch {
4825
+ return false;
4826
+ }
4827
+ }
4828
+ function readDirSyncSafe(dir) {
4829
+ try {
4830
+ return readdirSync7(dir);
4831
+ } catch {
4832
+ return [];
4833
+ }
4834
+ }
4835
+ function readJsonManifest(path) {
4836
+ try {
4837
+ const raw = readFileSync15(path, "utf8");
4838
+ return JSON.parse(raw);
4839
+ } catch {
4840
+ return void 0;
4841
+ }
4842
+ }
4843
+
3653
4844
  // src/interactive/interactive-session.ts
3654
4845
  var InteractiveSession = class {
3655
4846
  session = null;
@@ -3673,6 +4864,9 @@ var InteractiveSession = class {
3673
4864
  backgroundTaskEvents = [];
3674
4865
  backgroundJobGroups = [];
3675
4866
  backgroundJobGroupEvents = [];
4867
+ memoryEvents = [];
4868
+ usedMemoryReferences = [];
4869
+ editCheckpointStore = null;
3676
4870
  resumeSessionId;
3677
4871
  forkSession;
3678
4872
  backgroundTaskUnsubscribe = null;
@@ -3690,6 +4884,9 @@ var InteractiveSession = class {
3690
4884
  this.sessionStore = options.sessionStore;
3691
4885
  this.sessionName = options.sessionName;
3692
4886
  this.cwd = ("cwd" in options ? options.cwd : void 0) ?? "";
4887
+ if ("session" in options && options.session && this.cwd) {
4888
+ this.editCheckpointStore = new EditCheckpointStore({ cwd: this.cwd });
4889
+ }
3693
4890
  this.resumeSessionId = options.resumeSessionId;
3694
4891
  this.forkSession = options.forkSession ?? false;
3695
4892
  if ("session" in options && options.session) {
@@ -3712,21 +4909,27 @@ var InteractiveSession = class {
3712
4909
  this.backgroundTaskEvents = restored.backgroundTaskEvents;
3713
4910
  this.backgroundJobGroups = restored.backgroundJobGroups;
3714
4911
  this.backgroundJobGroupEvents = restored.backgroundJobGroupEvents;
4912
+ this.memoryEvents = restored.memoryEvents;
4913
+ this.usedMemoryReferences = restored.usedMemoryReferences;
3715
4914
  this.pendingRestoreMessages = restored.pendingRestoreMessages;
3716
4915
  }
3717
4916
  if (this.initialized) this.subscribeBackgroundTaskEvents();
3718
4917
  if (this.initialized) this.persistCurrentSession();
3719
4918
  }
3720
4919
  async initializeAsync(options) {
4920
+ const config = await loadConfig(options.cwd);
4921
+ this.editCheckpointStore = new EditCheckpointStore({ cwd: options.cwd });
3721
4922
  this.session = await createInteractiveSession({
3722
4923
  cwd: options.cwd,
3723
4924
  provider: options.provider,
4925
+ config,
3724
4926
  permissionMode: options.permissionMode,
3725
4927
  maxTurns: options.maxTurns,
3726
4928
  permissionHandler: options.permissionHandler,
3727
4929
  resumeSessionId: this.resumeSessionId,
3728
4930
  forkSession: this.forkSession,
3729
4931
  onTextDelta: (delta) => this.handleTextDelta(delta),
4932
+ onContextUpdate: (state) => this.emit("context_update", state),
3730
4933
  onToolExecution: (event) => this.handleToolExecution(event),
3731
4934
  bare: options.bare,
3732
4935
  allowedTools: options.allowedTools,
@@ -3734,6 +4937,7 @@ var InteractiveSession = class {
3734
4937
  backgroundTaskRunners: options.backgroundTaskRunners,
3735
4938
  subagentRunnerFactory: options.subagentRunnerFactory,
3736
4939
  ...options.commandModules ? { commandModules: options.commandModules } : {},
4940
+ editCheckpointRecorder: this.editCheckpointStore,
3737
4941
  commandDescriptors: this.commandExecutor.listModelInvocableCommands(),
3738
4942
  ...this.commandExecutor.listModelInvocableCommands().length > 0 ? {
3739
4943
  modelCommandExecutor: (command, args) => this.executeModelCommand(command, args),
@@ -3876,9 +5080,38 @@ var InteractiveSession = class {
3876
5080
  getName() {
3877
5081
  return this.sessionName;
3878
5082
  }
5083
+ getCwd() {
5084
+ return this.cwd ?? process.cwd();
5085
+ }
3879
5086
  getSession() {
3880
5087
  return this.getSessionOrThrow();
3881
5088
  }
5089
+ listEditCheckpoints() {
5090
+ const sessionId = this.getSessionOrThrow().getSessionId();
5091
+ return this.getEditCheckpointStore().list(sessionId);
5092
+ }
5093
+ async restoreEditCheckpoint(checkpointId) {
5094
+ await this.ensureInitialized();
5095
+ if (this.executing) {
5096
+ throw new Error("Cannot restore edit checkpoint while a prompt is running.");
5097
+ }
5098
+ const result = await this.getEditCheckpointStore().restoreToCheckpoint(
5099
+ this.getSessionOrThrow().getSessionId(),
5100
+ checkpointId
5101
+ );
5102
+ this.history.push(
5103
+ messageToHistoryEntry(createSystemMessage(`Restored edit checkpoint: ${checkpointId}`))
5104
+ );
5105
+ this.persistCurrentSession();
5106
+ return result;
5107
+ }
5108
+ getUsedMemoryReferences() {
5109
+ return [...this.usedMemoryReferences];
5110
+ }
5111
+ recordMemoryEvent(event) {
5112
+ this.memoryEvents.push(event);
5113
+ this.persistCurrentSession();
5114
+ }
3882
5115
  listBackgroundTasks(filter) {
3883
5116
  return this.getBackgroundTaskManagerOrThrow().list(filter);
3884
5117
  }
@@ -4101,6 +5334,10 @@ var InteractiveSession = class {
4101
5334
  events: this.backgroundTaskEvents,
4102
5335
  groups: this.backgroundJobGroups,
4103
5336
  groupEvents: this.backgroundJobGroupEvents
5337
+ },
5338
+ {
5339
+ events: this.memoryEvents,
5340
+ usedReferences: this.usedMemoryReferences
4104
5341
  }
4105
5342
  );
4106
5343
  }
@@ -4182,7 +5419,9 @@ var InteractiveSession = class {
4182
5419
  this.emit("thinking", true);
4183
5420
  this.history.push(messageToHistoryEntry(createUserMessage(displayInput ?? input)));
4184
5421
  const historyBefore = this.getSessionOrThrow().getHistory().length;
5422
+ this.usedMemoryReferences = [];
4185
5423
  try {
5424
+ await this.beginEditCheckpointTurn(displayInput ?? input);
4186
5425
  const response = await this.getSessionOrThrow().run(input, rawInput);
4187
5426
  this.flushStreaming();
4188
5427
  pushToolSummaryToHistory({ activeTools: this.activeTools, history: this.history });
@@ -4195,6 +5434,7 @@ var InteractiveSession = class {
4195
5434
  this.getContextState()
4196
5435
  );
4197
5436
  this.history.push(messageToHistoryEntry(createAssistantMessage(result.response)));
5437
+ if (result.usage) this.history.push(createUsageSummaryEntry(result.usage));
4198
5438
  this.emit("complete", result);
4199
5439
  this.emit("context_update", this.getContextState());
4200
5440
  } catch (err) {
@@ -4210,6 +5450,7 @@ var InteractiveSession = class {
4210
5450
  this.clearStreaming();
4211
5451
  if (result.response)
4212
5452
  this.history.push(messageToHistoryEntry(createAssistantMessage(result.response)));
5453
+ if (result.usage) this.history.push(createUsageSummaryEntry(result.usage));
4213
5454
  this.history.push(messageToHistoryEntry(createSystemMessage("Interrupted by user.")));
4214
5455
  this.emit("interrupted", result);
4215
5456
  } else {
@@ -4220,6 +5461,7 @@ var InteractiveSession = class {
4220
5461
  this.emit("error", err instanceof Error ? err : new Error(errMsg));
4221
5462
  }
4222
5463
  } finally {
5464
+ await this.finalizeEditCheckpointTurn();
4223
5465
  this.executing = false;
4224
5466
  this.emit("thinking", false);
4225
5467
  this.persistCurrentSession();
@@ -4232,6 +5474,31 @@ var InteractiveSession = class {
4232
5474
  }
4233
5475
  }
4234
5476
  }
5477
+ getEditCheckpointStore() {
5478
+ if (!this.editCheckpointStore) {
5479
+ this.editCheckpointStore = new EditCheckpointStore({ cwd: this.getCwd() });
5480
+ }
5481
+ return this.editCheckpointStore;
5482
+ }
5483
+ async beginEditCheckpointTurn(prompt) {
5484
+ if (!this.editCheckpointStore) return;
5485
+ await this.editCheckpointStore.beginTurn({
5486
+ sessionId: this.getSessionOrThrow().getSessionId(),
5487
+ prompt
5488
+ });
5489
+ }
5490
+ async finalizeEditCheckpointTurn() {
5491
+ if (!this.editCheckpointStore) return;
5492
+ try {
5493
+ await this.editCheckpointStore.finalizeTurn();
5494
+ } catch (error) {
5495
+ const err = error instanceof Error ? error : new Error(String(error));
5496
+ this.history.push(
5497
+ messageToHistoryEntry(createSystemMessage(`Checkpoint error: ${err.message}`))
5498
+ );
5499
+ this.emit("error", err);
5500
+ }
5501
+ }
4235
5502
  handleTextDelta(delta) {
4236
5503
  this.streamingText += delta;
4237
5504
  this.emit("text_delta", delta);
@@ -4282,14 +5549,14 @@ function createQuery(options) {
4282
5549
  session.on("text_delta", options.onTextDelta);
4283
5550
  }
4284
5551
  return async (prompt) => {
4285
- return new Promise((resolve2, reject) => {
5552
+ return new Promise((resolve4, reject) => {
4286
5553
  const onComplete = (result) => {
4287
5554
  cleanup();
4288
- resolve2(result.response);
5555
+ resolve4(result.response);
4289
5556
  };
4290
5557
  const onInterrupted = (result) => {
4291
5558
  cleanup();
4292
- resolve2(result.response);
5559
+ resolve4(result.response);
4293
5560
  };
4294
5561
  const onError = (error) => {
4295
5562
  cleanup();
@@ -4322,6 +5589,121 @@ import {
4322
5589
  getMessagesForAPI
4323
5590
  } from "@robota-sdk/agent-core";
4324
5591
 
5592
+ // src/self-hosting/self-hosting-verification.ts
5593
+ var DEFAULT_BASE_REF = "origin/develop";
5594
+ var PACKAGE_VERIFY_COMMANDS = ["test", "typecheck", "build"];
5595
+ var TRANSITIONS = {
5596
+ idle: {
5597
+ checkpoint_created: "checkpointed",
5598
+ cancelled: "cancelled"
5599
+ },
5600
+ checkpointed: {
5601
+ edits_started: "editing",
5602
+ cancelled: "cancelled"
5603
+ },
5604
+ editing: {
5605
+ edits_applied: "verifying",
5606
+ verify_failed: "failed",
5607
+ cancelled: "cancelled"
5608
+ },
5609
+ verifying: {
5610
+ verify_passed: "passed",
5611
+ verify_failed: "failed",
5612
+ cancelled: "cancelled"
5613
+ },
5614
+ passed: {},
5615
+ failed: {
5616
+ rollback_completed: "rolled_back",
5617
+ cancelled: "cancelled"
5618
+ },
5619
+ rolled_back: {},
5620
+ cancelled: {}
5621
+ };
5622
+ function normalizePackageScopes(packageScopes) {
5623
+ if (!packageScopes) {
5624
+ return [];
5625
+ }
5626
+ return Array.from(new Set(packageScopes.map((scope) => scope.trim()).filter(Boolean)));
5627
+ }
5628
+ function packageVerificationSteps(packageScopes) {
5629
+ return packageScopes.flatMap(
5630
+ (scope) => PACKAGE_VERIFY_COMMANDS.map(
5631
+ (commandName) => ({
5632
+ id: `package-${commandName}:${scope}`,
5633
+ phase: "verify",
5634
+ description: `Run ${commandName} for ${scope} in a child process against the new on-disk tree.`,
5635
+ required: true,
5636
+ command: `pnpm --filter ${scope} ${commandName}`
5637
+ })
5638
+ )
5639
+ );
5640
+ }
5641
+ function preVerificationSteps() {
5642
+ return [
5643
+ {
5644
+ id: "checkpoint",
5645
+ phase: "checkpoint",
5646
+ description: "Create a recoverable turn-level checkpoint before the first mutation.",
5647
+ required: true
5648
+ },
5649
+ {
5650
+ id: "atomic-edit",
5651
+ phase: "edit",
5652
+ description: "Apply Write/Edit mutations through same-directory temp files and atomic rename.",
5653
+ required: true
5654
+ },
5655
+ {
5656
+ id: "handoff",
5657
+ phase: "handoff",
5658
+ description: "Keep the current process on already-loaded code and run verification child processes against disk.",
5659
+ required: true
5660
+ }
5661
+ ];
5662
+ }
5663
+ function harnessVerificationStep(baseRef) {
5664
+ return {
5665
+ id: "harness-verify",
5666
+ phase: "verify",
5667
+ description: "Run Robota harness verification as the local CI-like gate.",
5668
+ required: true,
5669
+ command: `pnpm harness:verify -- --base-ref ${baseRef} --skip-record-check`
5670
+ };
5671
+ }
5672
+ function rollbackRecoveryStep() {
5673
+ return {
5674
+ id: "rollback-on-failure",
5675
+ phase: "recover",
5676
+ description: "Use the existing edit checkpoint restore path if verification fails.",
5677
+ required: true
5678
+ };
5679
+ }
5680
+ function planSelfHostingVerification(input) {
5681
+ if (input.changedFiles.length === 0) {
5682
+ throw new Error("Self-hosting verification requires at least one changed file.");
5683
+ }
5684
+ const baseRef = input.baseRef ?? DEFAULT_BASE_REF;
5685
+ const packageScopes = normalizePackageScopes(input.packageScopes);
5686
+ const steps = [
5687
+ ...preVerificationSteps(),
5688
+ ...packageVerificationSteps(packageScopes),
5689
+ harnessVerificationStep(baseRef),
5690
+ rollbackRecoveryStep()
5691
+ ];
5692
+ return {
5693
+ changedFiles: [...input.changedFiles],
5694
+ packageScopes,
5695
+ baseRef,
5696
+ steps
5697
+ };
5698
+ }
5699
+ function transitionSelfHostingLoop(state, event) {
5700
+ const nextState = TRANSITIONS[state][event];
5701
+ if (!nextState) {
5702
+ throw new Error(`Invalid self-hosting loop transition: ${state} -> ${event}`);
5703
+ }
5704
+ return nextState;
5705
+ }
5706
+
4325
5707
  // src/subagents/index.ts
4326
5708
  import { SubagentManager as SubagentManager3 } from "@robota-sdk/agent-runtime";
4327
5709
  import { WorktreeSubagentRunner, createWorktreeSubagentRunner } from "@robota-sdk/agent-runtime";
@@ -4361,10 +5743,14 @@ export {
4361
5743
  BundlePluginInstaller,
4362
5744
  BundlePluginLoader,
4363
5745
  CommandRegistry,
5746
+ EditCheckpointStore,
4364
5747
  InteractiveSession,
5748
+ MEMORY_INDEX_MAX_BYTES,
5749
+ MEMORY_INDEX_MAX_LINES,
4365
5750
  MarketplaceClient,
4366
5751
  PluginCommandSource,
4367
5752
  PluginSettingsStore,
5753
+ ProjectMemoryStore,
4368
5754
  PromptExecutor,
4369
5755
  SkillCommandSource,
4370
5756
  SubagentManager3 as SubagentManager,
@@ -4383,26 +5769,37 @@ export {
4383
5769
  createSubagentSession,
4384
5770
  createSystemCommands,
4385
5771
  createWorktreeSubagentRunner,
5772
+ discoverTaskFiles,
4386
5773
  evaluatePermission,
4387
5774
  executeSkill,
5775
+ formatTaskContext,
4388
5776
  getBackgroundTaskTransitions,
4389
5777
  getBuiltInAgent,
4390
5778
  getForkWorkerSuffix,
4391
5779
  getMessagesForAPI,
4392
5780
  getSubagentSuffix,
4393
5781
  isChatEntry,
5782
+ isMemoryType,
4394
5783
  isTerminalBackgroundTaskStatus2 as isTerminalBackgroundTaskStatus,
5784
+ loadTaskContext,
4395
5785
  messageToHistoryEntry2 as messageToHistoryEntry,
4396
5786
  parseFrontmatter,
5787
+ parseTaskFile,
5788
+ planSelfHostingVerification,
4397
5789
  preprocessShellCommands,
4398
5790
  projectPaths,
4399
5791
  promptForApproval,
5792
+ readCurrentGitBranch,
4400
5793
  resolveSubagentLogDir,
4401
5794
  retrieveAgentToolDeps,
4402
5795
  runHooks2 as runHooks,
5796
+ selectRelevantTasks,
4403
5797
  storeAgentToolDeps,
4404
5798
  substituteVariables,
4405
5799
  summarizeBackgroundJobGroup,
4406
5800
  transitionBackgroundTaskStatus,
4407
- userPaths
5801
+ transitionSelfHostingLoop,
5802
+ updateTaskFileStatus,
5803
+ userPaths,
5804
+ wrapEditCheckpointTools
4408
5805
  };