@robota-sdk/agent-sdk 3.0.0-beta.56 → 3.0.0-beta.58
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -4
- package/dist/node/index.cjs +1966 -561
- package/dist/node/index.d.cts +232 -19
- package/dist/node/index.d.ts +232 -19
- package/dist/node/index.js +1938 -541
- package/package.json +6 -5
package/dist/node/index.js
CHANGED
|
@@ -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
|
|
969
|
-
"
|
|
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
|
|
981
|
-
"
|
|
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
|
|
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(
|
|
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
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
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((
|
|
1178
|
-
resolveGroup =
|
|
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
|
|
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:
|
|
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:
|
|
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 = {
|
|
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:
|
|
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
|
|
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 =
|
|
1765
|
-
command:
|
|
1766
|
-
timeout:
|
|
1767
|
-
workingDirectory:
|
|
1768
|
-
stdin:
|
|
1769
|
-
outputLimitBytes:
|
|
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
|
|
2873
|
+
import { z as z4 } from "zod";
|
|
1819
2874
|
import { createZodFunctionTool as createZodFunctionTool3 } from "@robota-sdk/agent-tools";
|
|
1820
|
-
var CommandExecutionSchema =
|
|
1821
|
-
command:
|
|
1822
|
-
args:
|
|
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
|
|
1870
|
-
import { join as
|
|
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 (!
|
|
3013
|
+
if (!existsSync5(dir)) return [];
|
|
1916
3014
|
const agents = [];
|
|
1917
3015
|
let entries;
|
|
1918
3016
|
try {
|
|
1919
|
-
entries =
|
|
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 =
|
|
1926
|
-
const content =
|
|
3023
|
+
const filePath = join5(dir, entry.name);
|
|
3024
|
+
const content = readFileSync6(filePath, "utf-8");
|
|
1927
3025
|
const { frontmatter, body } = parseFrontmatter2(content);
|
|
1928
|
-
const fallbackName =
|
|
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(
|
|
1954
|
-
scanAgentsDir(
|
|
1955
|
-
scanAgentsDir(
|
|
1956
|
-
scanAgentsDir(
|
|
1957
|
-
scanAgentsDir(
|
|
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
|
|
2215
|
-
import { FileSessionLogger } from "@robota-sdk/agent-sessions";
|
|
2216
|
-
function createSubagentLogger(parentSessionId, _agentId, baseLogsDir) {
|
|
2217
|
-
const subagentDir =
|
|
2218
|
-
|
|
2219
|
-
return new FileSessionLogger(subagentDir);
|
|
2220
|
-
}
|
|
2221
|
-
function resolveSubagentLogDir(parentSessionId, baseLogsDir) {
|
|
2222
|
-
return
|
|
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
|
|
2230
|
-
import { homedir as homedir3 } from "os";
|
|
2231
|
-
function projectPaths(cwd) {
|
|
2232
|
-
const base =
|
|
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
|
-
|
|
2415
|
-
|
|
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
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
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
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
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
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
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
|
-
|
|
2455
|
-
|
|
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
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
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
|
|
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
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
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
|
|
2490
|
-
const
|
|
2491
|
-
return [
|
|
2492
|
-
|
|
2493
|
-
|
|
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
|
-
|
|
2507
|
-
|
|
2508
|
-
|
|
2509
|
-
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
const
|
|
2516
|
-
|
|
2517
|
-
|
|
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 =
|
|
3529
|
+
let current = resolve2(startDir);
|
|
2534
3530
|
let atRoot = false;
|
|
2535
3531
|
while (!atRoot) {
|
|
2536
|
-
const candidate =
|
|
2537
|
-
if (
|
|
3532
|
+
const candidate = join9(current, filename);
|
|
3533
|
+
if (existsSync7(candidate)) {
|
|
2538
3534
|
found.push(candidate);
|
|
2539
3535
|
}
|
|
2540
|
-
const parent =
|
|
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) =>
|
|
2576
|
-
const claudeMd = claudePaths.map((p) =>
|
|
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
|
-
|
|
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
|
|
2583
|
-
import { join as
|
|
3582
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
|
|
3583
|
+
import { join as join10 } from "path";
|
|
2584
3584
|
function tryReadJson(filePath) {
|
|
2585
|
-
if (!
|
|
3585
|
+
if (!existsSync8(filePath)) return void 0;
|
|
2586
3586
|
try {
|
|
2587
|
-
return JSON.parse(
|
|
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 (
|
|
3593
|
+
if (existsSync8(join10(cwd, "pnpm-workspace.yaml")) || existsSync8(join10(cwd, "pnpm-lock.yaml"))) {
|
|
2594
3594
|
return "pnpm";
|
|
2595
3595
|
}
|
|
2596
|
-
if (
|
|
3596
|
+
if (existsSync8(join10(cwd, "yarn.lock"))) {
|
|
2597
3597
|
return "yarn";
|
|
2598
3598
|
}
|
|
2599
|
-
if (
|
|
3599
|
+
if (existsSync8(join10(cwd, "bun.lockb"))) {
|
|
2600
3600
|
return "bun";
|
|
2601
3601
|
}
|
|
2602
|
-
if (
|
|
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 =
|
|
2609
|
-
const tsconfigPath =
|
|
2610
|
-
const pyprojectPath =
|
|
2611
|
-
const cargoPath =
|
|
2612
|
-
const goModPath =
|
|
2613
|
-
if (
|
|
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 =
|
|
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 (
|
|
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 (
|
|
3630
|
+
if (existsSync8(cargoPath)) {
|
|
2631
3631
|
return {
|
|
2632
3632
|
type: "rust",
|
|
2633
3633
|
language: "rust"
|
|
2634
3634
|
};
|
|
2635
3635
|
}
|
|
2636
|
-
if (
|
|
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
|
|
2650
|
-
import { dirname as
|
|
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 (!
|
|
3658
|
+
if (!existsSync9(this.settingsPath)) {
|
|
2659
3659
|
return {};
|
|
2660
3660
|
}
|
|
2661
3661
|
try {
|
|
2662
|
-
const raw =
|
|
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 =
|
|
2675
|
-
if (!
|
|
2676
|
-
|
|
3674
|
+
const dir = dirname4(this.settingsPath);
|
|
3675
|
+
if (!existsSync9(dir)) {
|
|
3676
|
+
mkdirSync4(dir, { recursive: true });
|
|
2677
3677
|
}
|
|
2678
|
-
|
|
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
|
|
2751
|
-
import { join as
|
|
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
|
|
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 (!
|
|
3803
|
+
if (!existsSync10(dirPath)) return [];
|
|
2804
3804
|
try {
|
|
2805
|
-
const entries =
|
|
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 =
|
|
2836
|
-
if (!
|
|
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 =
|
|
3842
|
+
const marketplaceDir = join11(cacheDir, marketplace);
|
|
2843
3843
|
const plugins = getSortedSubdirs(marketplaceDir);
|
|
2844
3844
|
for (const pluginName of plugins) {
|
|
2845
|
-
const pluginDir =
|
|
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 =
|
|
2850
|
-
const manifestPath =
|
|
2851
|
-
if (!
|
|
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 =
|
|
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 =
|
|
2901
|
-
if (!
|
|
2902
|
-
const entries =
|
|
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 =
|
|
2907
|
-
if (!
|
|
2908
|
-
const raw =
|
|
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 =
|
|
2924
|
-
if (!
|
|
2925
|
-
const entries =
|
|
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 =
|
|
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 =
|
|
2945
|
-
if (!
|
|
3944
|
+
const hooksPath = join11(pluginDir, "hooks", "hooks.json");
|
|
3945
|
+
if (!existsSync11(hooksPath)) return {};
|
|
2946
3946
|
try {
|
|
2947
|
-
const raw =
|
|
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 =
|
|
2960
|
-
const fallbackPath =
|
|
2961
|
-
const mcpPath =
|
|
2962
|
-
if (!
|
|
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 =
|
|
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 =
|
|
2973
|
-
if (!
|
|
3972
|
+
const agentsDir = join11(pluginDir, "agents");
|
|
3973
|
+
if (!existsSync11(agentsDir)) return [];
|
|
2974
3974
|
try {
|
|
2975
|
-
const entries =
|
|
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
|
|
2986
|
-
import { join as
|
|
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 =
|
|
2998
|
-
this.registryPath =
|
|
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 =
|
|
3019
|
-
if (
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
3099
|
-
if (!
|
|
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 (
|
|
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 (!
|
|
4135
|
+
if (!existsSync12(this.registryPath)) {
|
|
3136
4136
|
return {};
|
|
3137
4137
|
}
|
|
3138
4138
|
try {
|
|
3139
|
-
const raw =
|
|
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 =
|
|
3152
|
-
if (!
|
|
3153
|
-
|
|
4151
|
+
const dir = dirname5(this.registryPath);
|
|
4152
|
+
if (!existsSync12(dir)) {
|
|
4153
|
+
mkdirSync5(dir, { recursive: true });
|
|
3154
4154
|
}
|
|
3155
|
-
|
|
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
|
|
3166
|
-
import { join as
|
|
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
|
|
3170
|
-
import { join as
|
|
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 (!
|
|
4172
|
+
if (!existsSync13(registryPath)) {
|
|
3173
4173
|
return {};
|
|
3174
4174
|
}
|
|
3175
4175
|
try {
|
|
3176
|
-
const raw =
|
|
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 =
|
|
3188
|
-
if (!
|
|
3189
|
-
|
|
4187
|
+
const dir = dirname6(registryPath);
|
|
4188
|
+
if (!existsSync13(dir)) {
|
|
4189
|
+
mkdirSync6(dir, { recursive: true });
|
|
3190
4190
|
}
|
|
3191
|
-
|
|
4191
|
+
writeFileSync6(registryPath, JSON.stringify(registry, null, 2), "utf-8");
|
|
3192
4192
|
}
|
|
3193
4193
|
function removeInstalledPluginsForMarketplace(pluginsDir, marketplaceName) {
|
|
3194
|
-
const installedPath =
|
|
3195
|
-
if (!
|
|
4194
|
+
const installedPath = join13(pluginsDir, "installed_plugins.json");
|
|
4195
|
+
if (!existsSync13(installedPath)) return;
|
|
3196
4196
|
let registry;
|
|
3197
4197
|
try {
|
|
3198
|
-
const raw =
|
|
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 &&
|
|
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 =
|
|
3217
|
-
if (!
|
|
3218
|
-
|
|
4216
|
+
const dir = dirname6(installedPath);
|
|
4217
|
+
if (!existsSync13(dir)) {
|
|
4218
|
+
mkdirSync6(dir, { recursive: true });
|
|
3219
4219
|
}
|
|
3220
|
-
|
|
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 =
|
|
3235
|
-
this.registryPath =
|
|
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 =
|
|
3249
|
-
|
|
4248
|
+
const tempDir = join14(this.marketplacesDir, tempName);
|
|
4249
|
+
mkdirSync7(this.marketplacesDir, { recursive: true });
|
|
3250
4250
|
if (source.type === "local") {
|
|
3251
|
-
if (!
|
|
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 =
|
|
3266
|
-
if (!
|
|
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 =
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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 =
|
|
3361
|
-
if (!
|
|
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 =
|
|
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
|
|
4442
|
+
import { join as join15, dirname as dirname7 } from "path";
|
|
3443
4443
|
function buildPluginEnv(plugin) {
|
|
3444
|
-
const dataDir =
|
|
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
|
|
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 =
|
|
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((
|
|
5552
|
+
return new Promise((resolve4, reject) => {
|
|
4286
5553
|
const onComplete = (result) => {
|
|
4287
5554
|
cleanup();
|
|
4288
|
-
|
|
5555
|
+
resolve4(result.response);
|
|
4289
5556
|
};
|
|
4290
5557
|
const onInterrupted = (result) => {
|
|
4291
5558
|
cleanup();
|
|
4292
|
-
|
|
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
|
-
|
|
5801
|
+
transitionSelfHostingLoop,
|
|
5802
|
+
updateTaskFileStatus,
|
|
5803
|
+
userPaths,
|
|
5804
|
+
wrapEditCheckpointTools
|
|
4408
5805
|
};
|