thoth-plugin 1.1.0 → 1.1.2
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 +237 -31
- package/dist/cli.d.ts +3 -2
- package/dist/cli.js +248 -5
- package/dist/config/schema.d.ts +2 -0
- package/dist/defaults/skill/email-draft/skill.md +134 -0
- package/dist/defaults/skill/evening-close/SKILL.md +7 -0
- package/dist/defaults/skill/gardener/SKILL.md +36 -1
- package/dist/defaults/skill/leadership-coach/SKILL.md +7 -0
- package/dist/defaults/skill/mail-triage/SKILL.md +7 -0
- package/dist/defaults/skill/morning-boot/SKILL.md +8 -0
- package/dist/defaults/skill/post-meeting-drill/SKILL.md +6 -0
- package/dist/defaults/skill/skill-generator/SKILL.md +150 -28
- package/dist/defaults/skill/slack-pulse/SKILL.md +6 -0
- package/dist/defaults/skill/thought-router/SKILL.md +7 -0
- package/dist/hooks/frontmatter-enforcer.d.ts +24 -0
- package/dist/hooks/index.d.ts +1 -0
- package/dist/index.js +477 -132
- package/dist/tools/skill/types.d.ts +2 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -405,37 +405,211 @@ Before stating any fact about Zeus's life:
|
|
|
405
405
|
- *If NOT FOUND*: Say "I don't have that recorded" \u2014 never guess
|
|
406
406
|
</Knowledge_Management>`;
|
|
407
407
|
// src/specialization/prompt-builder.ts
|
|
408
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
408
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync } from "fs";
|
|
409
|
+
import { join as join3 } from "path";
|
|
410
|
+
import { homedir } from "os";
|
|
411
|
+
function parseSkillTriggers(content) {
|
|
412
|
+
const frontmatterRegex = /^---\r?\n([\s\S]*?)\r?\n---/;
|
|
413
|
+
const match = content.match(frontmatterRegex);
|
|
414
|
+
if (!match)
|
|
415
|
+
return null;
|
|
416
|
+
const yaml = match[1];
|
|
417
|
+
let name = "";
|
|
418
|
+
const triggers = [];
|
|
419
|
+
let inTriggers = false;
|
|
420
|
+
for (const line of yaml.split(`
|
|
421
|
+
`)) {
|
|
422
|
+
const trimmed = line.trim();
|
|
423
|
+
if (trimmed.startsWith("name:")) {
|
|
424
|
+
name = trimmed.slice(5).trim();
|
|
425
|
+
inTriggers = false;
|
|
426
|
+
} else if (trimmed === "triggers:") {
|
|
427
|
+
inTriggers = true;
|
|
428
|
+
} else if (inTriggers && trimmed.startsWith("- ")) {
|
|
429
|
+
triggers.push(trimmed.slice(2).trim());
|
|
430
|
+
} else if (trimmed.includes(":") && !trimmed.startsWith("-")) {
|
|
431
|
+
inTriggers = false;
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return triggers.length > 0 ? { name, triggers } : null;
|
|
435
|
+
}
|
|
436
|
+
function discoverTriggersFromDir(skillsDir) {
|
|
437
|
+
if (!existsSync3(skillsDir))
|
|
438
|
+
return [];
|
|
439
|
+
const results = [];
|
|
440
|
+
try {
|
|
441
|
+
const entries = readdirSync(skillsDir, { withFileTypes: true });
|
|
442
|
+
for (const entry of entries) {
|
|
443
|
+
if (entry.name.startsWith("."))
|
|
444
|
+
continue;
|
|
445
|
+
if (!entry.isDirectory())
|
|
446
|
+
continue;
|
|
447
|
+
const skillMdPath = join3(skillsDir, entry.name, "SKILL.md");
|
|
448
|
+
if (!existsSync3(skillMdPath))
|
|
449
|
+
continue;
|
|
450
|
+
try {
|
|
451
|
+
const content = readFileSync2(skillMdPath, "utf-8");
|
|
452
|
+
const parsed = parseSkillTriggers(content);
|
|
453
|
+
if (parsed) {
|
|
454
|
+
results.push(parsed);
|
|
455
|
+
}
|
|
456
|
+
} catch {}
|
|
457
|
+
}
|
|
458
|
+
} catch {}
|
|
459
|
+
return results;
|
|
460
|
+
}
|
|
461
|
+
function buildSkillRoutingSection() {
|
|
462
|
+
const projectSkillsDir = join3(process.cwd(), ".opencode", "skill");
|
|
463
|
+
const userSkillsDir = join3(homedir(), ".opencode", "skill");
|
|
464
|
+
const projectTriggers = discoverTriggersFromDir(projectSkillsDir);
|
|
465
|
+
const userTriggers = discoverTriggersFromDir(userSkillsDir);
|
|
466
|
+
const seen = new Set;
|
|
467
|
+
const allTriggers = [];
|
|
468
|
+
for (const t of [...projectTriggers, ...userTriggers]) {
|
|
469
|
+
if (!seen.has(t.name)) {
|
|
470
|
+
seen.add(t.name);
|
|
471
|
+
allTriggers.push(t);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (allTriggers.length === 0) {
|
|
475
|
+
return "";
|
|
476
|
+
}
|
|
477
|
+
const lines = [
|
|
478
|
+
"<Skill_Routing>",
|
|
479
|
+
"## Skill Routing (CHECK BEFORE RESPONDING)",
|
|
480
|
+
"",
|
|
481
|
+
"Before responding to user requests, check if their intent matches a skill trigger:",
|
|
482
|
+
""
|
|
483
|
+
];
|
|
484
|
+
for (const skill of allTriggers) {
|
|
485
|
+
const triggers = skill.triggers.map((t) => `"${t}"`).join(", ");
|
|
486
|
+
lines.push(`- ${triggers} \u2192 \`skill({ skill: "${skill.name}" })\``);
|
|
487
|
+
}
|
|
488
|
+
lines.push("");
|
|
489
|
+
lines.push("**Rule**: If user intent matches a trigger, invoke the skill immediately. Don't improvise workflows that already exist.");
|
|
490
|
+
lines.push("</Skill_Routing>");
|
|
491
|
+
return lines.join(`
|
|
492
|
+
`);
|
|
493
|
+
}
|
|
494
|
+
var THOTH_INTENT_GATE = `<Phase_0_Intent_Gate>
|
|
495
|
+
## Phase 0: Intent Gate (EVERY prompt)
|
|
496
|
+
|
|
497
|
+
Before ANY action, classify the incoming request:
|
|
498
|
+
|
|
499
|
+
### Step 0: Check for Skills
|
|
500
|
+
| Trigger | Skill | Action |
|
|
501
|
+
|---------|-------|--------|
|
|
502
|
+
| "Run morning boot", "Start my day" | morning-boot | Fire skill immediately |
|
|
503
|
+
| "End of day", "Close out" | evening-close | Fire skill immediately |
|
|
504
|
+
| "Dump:", "Quick thought:" | thought-router | Fire skill immediately |
|
|
505
|
+
| "Drill meeting notes" | post-meeting-drill | Fire skill immediately |
|
|
506
|
+
|
|
507
|
+
### Step 1: Identify Hemisphere(s)
|
|
508
|
+
| Signal | Hemisphere |
|
|
509
|
+
|--------|------------|
|
|
510
|
+
| Code, technical, development, bugs, features, git | **coding/** |
|
|
511
|
+
| Work, job, colleagues, projects, meetings, career, stakeholders | **work/** |
|
|
512
|
+
| Personal, health, family, friends, home, finance, feelings | **life/** |
|
|
513
|
+
| System, settings, preferences, onboarding, meta, Thoth | **kernel/** |
|
|
514
|
+
| Ambiguous or cross-domain | Multiple or ask |
|
|
515
|
+
|
|
516
|
+
### Step 2: Check Permissions
|
|
517
|
+
Before any action, verify against kernel/config/permissions.md:
|
|
518
|
+
- **Autonomous**: Read, analyze, create knowledge, fire background agents
|
|
519
|
+
- **Requires Approval**: Send communications, financial, delete, modify shared files
|
|
520
|
+
|
|
521
|
+
### Step 3: Check Trust Level
|
|
522
|
+
Read kernel/state/trust.md for current trust level:
|
|
523
|
+
- **Level 1**: Read only, all writes require approval
|
|
524
|
+
- **Level 2**: Code edits with evidence, knowledge updates
|
|
525
|
+
- **Level 3**: Routine communications, calendar changes
|
|
526
|
+
|
|
527
|
+
### Step 4: Classify Request Type
|
|
528
|
+
| Type | Signal | Action |
|
|
529
|
+
|------|--------|--------|
|
|
530
|
+
| **Information** | "What is...", "Who is...", "Tell me about..." | Retrieve context \u2192 answer |
|
|
531
|
+
| **Action** | "Send...", "Schedule...", "Create...", "Update..." | Check permissions \u2192 execute or delegate |
|
|
532
|
+
| **Planning** | "Help me plan...", "How should I..." | Retrieve context \u2192 think \u2192 propose |
|
|
533
|
+
| **Coaching** | "Reflect", "How am I doing" | Thoughtful dialogue |
|
|
534
|
+
| **Onboarding** | "Let's onboard...", "Learn about my..." | Enter onboarding mode |
|
|
535
|
+
| **Meta** | "Update your prompt", "Change how you..." | Collaborative self-modification |
|
|
536
|
+
|
|
537
|
+
### Step 5: Determine Routing
|
|
538
|
+
| Complexity | Action |
|
|
539
|
+
|------------|--------|
|
|
540
|
+
| Simple, single-hemisphere | Handle directly with context from that hemisphere |
|
|
541
|
+
| Complex, single-hemisphere | Delegate to hemisphere Master |
|
|
542
|
+
| Cross-hemisphere | Orchestrate: gather context from multiple, synthesize |
|
|
543
|
+
| Requires research | Fire parallel background_task agents |
|
|
544
|
+
</Phase_0_Intent_Gate>`;
|
|
409
545
|
var THOTH_CORE_CAPABILITIES = `<Core_Capabilities>
|
|
410
|
-
## Core Capabilities
|
|
546
|
+
## Core Capabilities (Built-In)
|
|
411
547
|
|
|
412
|
-
|
|
548
|
+
You have these capabilities built into your core function. Do NOT delegate these to sub-agents \u2014 execute them directly with full session context.
|
|
413
549
|
|
|
414
|
-
|
|
415
|
-
|-----------|--------|
|
|
416
|
-
| Simple lookup, single file read | Execute directly |
|
|
417
|
-
| Knowledge base update | Execute directly |
|
|
418
|
-
| Parallel research (multiple sources) | Fire background agents |
|
|
419
|
-
| Complex workflow with defined steps | Invoke a skill |
|
|
420
|
-
| Deep domain work requiring focus | Delegate to sub-agent |
|
|
550
|
+
### Knowledge Retrieval (You Do This Directly)
|
|
421
551
|
|
|
422
|
-
|
|
552
|
+
When Zeus asks "What do I know about X?", "Who is Y?", "Context on Z?":
|
|
423
553
|
|
|
424
|
-
|
|
554
|
+
1. **Read \`kernel/paths.json\`** to locate files
|
|
555
|
+
2. **Follow Circle System** (see Phase 1 below)
|
|
556
|
+
3. **Synthesize** the relevant information
|
|
557
|
+
4. **Cite sources** \u2014 always reference which files you found information in
|
|
558
|
+
5. **Acknowledge gaps** \u2014 if information is missing, say so
|
|
425
559
|
|
|
426
|
-
|
|
427
|
-
background_task(agent="general", prompt="[Specific task instructions]...")
|
|
428
|
-
\`\`\`
|
|
560
|
+
### Knowledge Persistence (You Do This Directly)
|
|
429
561
|
|
|
430
|
-
|
|
562
|
+
When new information emerges that should be remembered:
|
|
431
563
|
|
|
432
|
-
|
|
564
|
+
1. **Read before write** \u2014 Always check existing content first
|
|
565
|
+
2. **Smart Merge** \u2014 Integrate into existing sections, don't just append
|
|
566
|
+
3. **Use templates** from \`kernel/templates/\` for new files
|
|
567
|
+
4. **Update registries** \u2014 Add new files to relevant \`_index.md\` and \`registry.md\`
|
|
568
|
+
5. **Bidirectional links** \u2014 If A references B, add A to B's related section
|
|
569
|
+
6. **Chronicle significant events** \u2014 Append to \`chronicle.md\` with date stamp
|
|
570
|
+
7. **Frontmatter required** \u2014 Every file needs type, hemisphere, dates, tags, summary
|
|
433
571
|
|
|
434
|
-
|
|
572
|
+
### Deduplication Check (Before Creating Files)
|
|
435
573
|
|
|
436
|
-
|
|
574
|
+
1. Grep for entity name across knowledge base
|
|
575
|
+
2. Check if file already exists
|
|
576
|
+
3. If exists \u2192 UPDATE, not CREATE
|
|
577
|
+
4. If similar exists \u2192 ASK for clarification
|
|
578
|
+
|
|
579
|
+
## Functional Agents (For Specialized Tasks)
|
|
580
|
+
|
|
581
|
+
These agents handle tasks that DON'T require session context:
|
|
582
|
+
|
|
583
|
+
| Agent | Function | When to Use |
|
|
584
|
+
|-------|----------|-------------|
|
|
585
|
+
| **coach** | Reflection & thinking partner | "Help me think through X", "I'm stuck on Y", "Should I do A or B?" |
|
|
586
|
+
| **sentinel** | Proactive monitoring | "What needs my attention?", "Any overdue items?", "Check my calendar" |
|
|
587
|
+
| **diplomat** | Communication drafting | "Draft an email to X", "Help me respond to Y", "How should I say Z?" |
|
|
588
|
+
| **chronicler** | Meeting/event processing | "Process this meeting", "Extract action items" (from provided notes) |
|
|
589
|
+
|
|
590
|
+
### When to Delegate vs Execute Directly
|
|
591
|
+
|
|
592
|
+
| Task | Action | Why |
|
|
593
|
+
|------|--------|-----|
|
|
594
|
+
| "What do I know about Sarah?" | **Execute directly** | Needs session context for relevance |
|
|
595
|
+
| "Remember that Sarah prefers async" | **Execute directly** | Needs session context for smart merge |
|
|
596
|
+
| "Help me think through this decision" | Delegate to **coach** | Specialized coaching framework |
|
|
597
|
+
| "What needs my attention today?" | Delegate to **sentinel** | Independent calendar/task scan |
|
|
598
|
+
| "Draft an email to Sarah" | Delegate to **diplomat** | Specialized communication drafting |
|
|
599
|
+
| "Process these meeting notes" | Delegate to **chronicler** | Structured extraction from provided text |
|
|
600
|
+
|
|
601
|
+
### Background Agents (Parallel, Independent)
|
|
437
602
|
|
|
438
|
-
For
|
|
603
|
+
For parallel research or data gathering, fire background agents:
|
|
604
|
+
|
|
605
|
+
\`\`\`typescript
|
|
606
|
+
// Example: Morning boot parallel scans
|
|
607
|
+
background_task(agent="general", prompt="[Email scan instructions]...")
|
|
608
|
+
background_task(agent="general", prompt="[Calendar scan instructions]...")
|
|
609
|
+
background_task(agent="general", prompt="[Slack scan instructions]...")
|
|
610
|
+
\`\`\`
|
|
611
|
+
|
|
612
|
+
These are appropriate because they gather independent data and return facts \u2014 they don't need session context.
|
|
439
613
|
</Core_Capabilities>`;
|
|
440
614
|
var THOTH_EXECUTION = `<Execution>
|
|
441
615
|
## Execution
|
|
@@ -468,6 +642,22 @@ A task is NOT complete without evidence:
|
|
|
468
642
|
| Test run | Pass (or note pre-existing failures) |
|
|
469
643
|
| Delegation | Agent result received and verified |
|
|
470
644
|
</Execution>`;
|
|
645
|
+
var THOTH_TEMPORAL_AWARENESS = `<Temporal_Awareness>
|
|
646
|
+
## Temporal Awareness (Chronos Protocol)
|
|
647
|
+
|
|
648
|
+
Operate with deep time awareness, respecting Zeus's biological and professional cycles.
|
|
649
|
+
|
|
650
|
+
### Executive Calendar (Work)
|
|
651
|
+
- **Monday**: Launch Mode \u2014 Prioritize planning, alignment, P0 definition
|
|
652
|
+
- **Tue-Thu**: Execution Mode \u2014 Protect deep work blocks, minimize admin
|
|
653
|
+
- **Friday**: Closure Mode \u2014 Wrap up, delegation follow-ups, weekly review
|
|
654
|
+
- **Weekend**: Sanctuary \u2014 Block work unless Emergency P0
|
|
655
|
+
|
|
656
|
+
### Biological Clock (Life)
|
|
657
|
+
- **Morning (08:00-11:00)**: High Cognitive \u2014 Protect from triage hell
|
|
658
|
+
- **Afternoon (14:00-17:00)**: Collaborative \u2014 Good for meetings and emails
|
|
659
|
+
- **Evening (19:00+)**: Restoration \u2014 Block work notifications, prompt journaling
|
|
660
|
+
</Temporal_Awareness>`;
|
|
471
661
|
var THOTH_PERMISSIONS = `<Permission_System>
|
|
472
662
|
## Permission System
|
|
473
663
|
|
|
@@ -539,9 +729,15 @@ function buildThothPrompt(spec) {
|
|
|
539
729
|
sections.push(THOTH_ANTI_PATTERNS);
|
|
540
730
|
sections.push(THOTH_BEHAVIORAL_GUIDANCE);
|
|
541
731
|
sections.push(THOTH_KNOWLEDGE_MANAGEMENT);
|
|
732
|
+
const skillRouting = buildSkillRoutingSection();
|
|
733
|
+
if (skillRouting) {
|
|
734
|
+
sections.push(skillRouting);
|
|
735
|
+
}
|
|
736
|
+
sections.push(THOTH_INTENT_GATE);
|
|
542
737
|
sections.push(THOTH_CORE_CAPABILITIES);
|
|
543
738
|
sections.push(THOTH_EXECUTION);
|
|
544
739
|
sections.push(THOTH_PERMISSIONS);
|
|
740
|
+
sections.push(THOTH_TEMPORAL_AWARENESS);
|
|
545
741
|
sections.push(THOTH_COMMUNICATION);
|
|
546
742
|
sections.push(THOTH_CLOSING);
|
|
547
743
|
return sections.join(`
|
|
@@ -3236,9 +3432,131 @@ To proceed, mark this as Emergency/P0 or wait until work hours.`
|
|
|
3236
3432
|
getDayModeRecommendation
|
|
3237
3433
|
};
|
|
3238
3434
|
}
|
|
3435
|
+
// src/hooks/frontmatter-enforcer.ts
|
|
3436
|
+
import * as path5 from "path";
|
|
3437
|
+
var FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
|
|
3438
|
+
function getToday() {
|
|
3439
|
+
return new Date().toISOString().split("T")[0];
|
|
3440
|
+
}
|
|
3441
|
+
function parseMarkdown(content) {
|
|
3442
|
+
const match = content.match(FRONTMATTER_REGEX);
|
|
3443
|
+
if (!match) {
|
|
3444
|
+
return { hasFrontmatter: false, frontmatter: {}, body: content };
|
|
3445
|
+
}
|
|
3446
|
+
const frontmatter = {};
|
|
3447
|
+
for (const line of match[1].split(`
|
|
3448
|
+
`)) {
|
|
3449
|
+
const colonIdx = line.indexOf(":");
|
|
3450
|
+
if (colonIdx === -1)
|
|
3451
|
+
continue;
|
|
3452
|
+
const key = line.slice(0, colonIdx).trim();
|
|
3453
|
+
let value = line.slice(colonIdx + 1).trim();
|
|
3454
|
+
if (value === "[]") {
|
|
3455
|
+
value = [];
|
|
3456
|
+
} else if (typeof value === "string") {
|
|
3457
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
3458
|
+
value = value.slice(1, -1);
|
|
3459
|
+
}
|
|
3460
|
+
}
|
|
3461
|
+
frontmatter[key] = value;
|
|
3462
|
+
}
|
|
3463
|
+
return {
|
|
3464
|
+
hasFrontmatter: true,
|
|
3465
|
+
frontmatter,
|
|
3466
|
+
body: content.slice(match[0].length)
|
|
3467
|
+
};
|
|
3468
|
+
}
|
|
3469
|
+
function serializeFrontmatter(data) {
|
|
3470
|
+
const lines = [];
|
|
3471
|
+
for (const [key, value] of Object.entries(data)) {
|
|
3472
|
+
if (Array.isArray(value)) {
|
|
3473
|
+
if (value.length === 0) {
|
|
3474
|
+
lines.push(`${key}: []`);
|
|
3475
|
+
} else {
|
|
3476
|
+
lines.push(`${key}:`);
|
|
3477
|
+
for (const item of value) {
|
|
3478
|
+
lines.push(` - ${item}`);
|
|
3479
|
+
}
|
|
3480
|
+
}
|
|
3481
|
+
} else if (typeof value === "string" && value.includes(":")) {
|
|
3482
|
+
lines.push(`${key}: "${value}"`);
|
|
3483
|
+
} else {
|
|
3484
|
+
lines.push(`${key}: ${value}`);
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
return lines.join(`
|
|
3488
|
+
`);
|
|
3489
|
+
}
|
|
3490
|
+
function reconstructMarkdown(frontmatter, body) {
|
|
3491
|
+
return `---
|
|
3492
|
+
${serializeFrontmatter(frontmatter)}
|
|
3493
|
+
---
|
|
3494
|
+
|
|
3495
|
+
${body.replace(/^\n+/, "")}`;
|
|
3496
|
+
}
|
|
3497
|
+
function ensureDates(content, created, updated) {
|
|
3498
|
+
const parsed = parseMarkdown(content);
|
|
3499
|
+
if (!parsed.hasFrontmatter) {
|
|
3500
|
+
return reconstructMarkdown({ created, updated }, content);
|
|
3501
|
+
}
|
|
3502
|
+
if (!parsed.frontmatter.created) {
|
|
3503
|
+
parsed.frontmatter.created = created;
|
|
3504
|
+
}
|
|
3505
|
+
parsed.frontmatter.updated = updated;
|
|
3506
|
+
return reconstructMarkdown(parsed.frontmatter, parsed.body);
|
|
3507
|
+
}
|
|
3508
|
+
function createFrontmatterEnforcerHook(config) {
|
|
3509
|
+
const { knowledgeBasePath, enabled = true } = config;
|
|
3510
|
+
if (!enabled)
|
|
3511
|
+
return null;
|
|
3512
|
+
const kbPath = expandPath(knowledgeBasePath);
|
|
3513
|
+
const pendingEdits = new Map;
|
|
3514
|
+
function isKbMarkdownFile(filePath) {
|
|
3515
|
+
if (!filePath.endsWith(".md"))
|
|
3516
|
+
return false;
|
|
3517
|
+
return path5.resolve(filePath).startsWith(path5.resolve(kbPath));
|
|
3518
|
+
}
|
|
3519
|
+
return {
|
|
3520
|
+
"tool.execute.before": async (input, output) => {
|
|
3521
|
+
const filePath = output.args.filePath;
|
|
3522
|
+
if (!filePath || !isKbMarkdownFile(filePath))
|
|
3523
|
+
return;
|
|
3524
|
+
if (input.tool === "write") {
|
|
3525
|
+
const content = output.args.content;
|
|
3526
|
+
if (!content)
|
|
3527
|
+
return;
|
|
3528
|
+
const today = getToday();
|
|
3529
|
+
const existing = readFileSync4(filePath);
|
|
3530
|
+
const createdDate = existing ? parseMarkdown(existing).frontmatter.created || today : today;
|
|
3531
|
+
output.args.content = ensureDates(content, createdDate, today);
|
|
3532
|
+
log(`Frontmatter: dates enforced for ${path5.basename(filePath)}`);
|
|
3533
|
+
}
|
|
3534
|
+
if (input.tool === "edit" && input.callID) {
|
|
3535
|
+
pendingEdits.set(input.callID, filePath);
|
|
3536
|
+
}
|
|
3537
|
+
},
|
|
3538
|
+
"tool.execute.after": async (input, _output) => {
|
|
3539
|
+
if (input.tool !== "edit")
|
|
3540
|
+
return;
|
|
3541
|
+
const filePath = pendingEdits.get(input.callID);
|
|
3542
|
+
pendingEdits.delete(input.callID);
|
|
3543
|
+
if (!filePath)
|
|
3544
|
+
return;
|
|
3545
|
+
const content = readFileSync4(filePath);
|
|
3546
|
+
if (!content)
|
|
3547
|
+
return;
|
|
3548
|
+
const parsed = parseMarkdown(content);
|
|
3549
|
+
if (!parsed.hasFrontmatter)
|
|
3550
|
+
return;
|
|
3551
|
+
parsed.frontmatter.updated = getToday();
|
|
3552
|
+
writeFileSync2(filePath, reconstructMarkdown(parsed.frontmatter, parsed.body));
|
|
3553
|
+
log(`Frontmatter: updated timestamp for ${path5.basename(filePath)}`);
|
|
3554
|
+
}
|
|
3555
|
+
};
|
|
3556
|
+
}
|
|
3239
3557
|
// src/hooks/directory-agents-injector/index.ts
|
|
3240
3558
|
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
3241
|
-
import { dirname as dirname4, join as
|
|
3559
|
+
import { dirname as dirname4, join as join9, resolve as resolve2 } from "path";
|
|
3242
3560
|
|
|
3243
3561
|
// src/hooks/directory-agents-injector/storage.ts
|
|
3244
3562
|
import {
|
|
@@ -3248,18 +3566,18 @@ import {
|
|
|
3248
3566
|
writeFileSync as writeFileSync3,
|
|
3249
3567
|
unlinkSync
|
|
3250
3568
|
} from "fs";
|
|
3251
|
-
import { join as
|
|
3569
|
+
import { join as join8 } from "path";
|
|
3252
3570
|
|
|
3253
3571
|
// src/hooks/directory-agents-injector/constants.ts
|
|
3254
|
-
import { join as
|
|
3255
|
-
var xdgData = process.env.XDG_DATA_HOME ||
|
|
3256
|
-
var OPENCODE_STORAGE =
|
|
3257
|
-
var AGENTS_INJECTOR_STORAGE =
|
|
3572
|
+
import { join as join7 } from "path";
|
|
3573
|
+
var xdgData = process.env.XDG_DATA_HOME || join7(process.env.HOME || "", ".local", "share");
|
|
3574
|
+
var OPENCODE_STORAGE = join7(xdgData, "opencode", "storage");
|
|
3575
|
+
var AGENTS_INJECTOR_STORAGE = join7(OPENCODE_STORAGE, "thoth-directory-agents");
|
|
3258
3576
|
var AGENTS_FILENAME2 = "AGENTS.md";
|
|
3259
3577
|
|
|
3260
3578
|
// src/hooks/directory-agents-injector/storage.ts
|
|
3261
3579
|
function getStoragePath(sessionID) {
|
|
3262
|
-
return
|
|
3580
|
+
return join8(AGENTS_INJECTOR_STORAGE, `${sessionID}.json`);
|
|
3263
3581
|
}
|
|
3264
3582
|
function loadInjectedPaths(sessionID) {
|
|
3265
3583
|
const filePath = getStoragePath(sessionID);
|
|
@@ -3308,14 +3626,14 @@ function createDirectoryAgentsInjectorHook(options) {
|
|
|
3308
3626
|
return null;
|
|
3309
3627
|
if (title.startsWith("/"))
|
|
3310
3628
|
return title;
|
|
3311
|
-
return
|
|
3629
|
+
return resolve2(directory, title);
|
|
3312
3630
|
}
|
|
3313
3631
|
function findAgentsMdUp(startDir) {
|
|
3314
3632
|
const found = [];
|
|
3315
3633
|
let current = startDir;
|
|
3316
3634
|
const normalizedBase = knowledgeBasePath.endsWith("/") ? knowledgeBasePath.slice(0, -1) : knowledgeBasePath;
|
|
3317
3635
|
while (true) {
|
|
3318
|
-
const agentsPath =
|
|
3636
|
+
const agentsPath = join9(current, AGENTS_FILENAME2);
|
|
3319
3637
|
if (existsSync6(agentsPath)) {
|
|
3320
3638
|
found.push(agentsPath);
|
|
3321
3639
|
}
|
|
@@ -3360,8 +3678,8 @@ function createDirectoryAgentsInjectorHook(options) {
|
|
|
3360
3678
|
}
|
|
3361
3679
|
if (toInject.length === 0)
|
|
3362
3680
|
return;
|
|
3363
|
-
for (const { path:
|
|
3364
|
-
const relativePath =
|
|
3681
|
+
for (const { path: path6, content } of toInject) {
|
|
3682
|
+
const relativePath = path6.replace(knowledgeBasePath, "").replace(/^\//, "");
|
|
3365
3683
|
output.output += `
|
|
3366
3684
|
|
|
3367
3685
|
[Directory Context: ${relativePath}]
|
|
@@ -3396,8 +3714,8 @@ ${content}`;
|
|
|
3396
3714
|
}
|
|
3397
3715
|
|
|
3398
3716
|
// src/shared-hooks/todo-continuation-enforcer.ts
|
|
3399
|
-
import { existsSync as existsSync8, readdirSync as
|
|
3400
|
-
import { join as
|
|
3717
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3 } from "fs";
|
|
3718
|
+
import { join as join13 } from "path";
|
|
3401
3719
|
|
|
3402
3720
|
// src/shared-hooks/utils/session-state.ts
|
|
3403
3721
|
var subagentSessions = new Set;
|
|
@@ -3410,16 +3728,16 @@ function getMainSessionID() {
|
|
|
3410
3728
|
}
|
|
3411
3729
|
|
|
3412
3730
|
// src/shared-hooks/utils/message-storage.ts
|
|
3413
|
-
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync, writeFileSync as writeFileSync4 } from "fs";
|
|
3414
|
-
import { join as
|
|
3731
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync as readdirSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
3732
|
+
import { join as join11 } from "path";
|
|
3415
3733
|
|
|
3416
3734
|
// src/shared-hooks/utils/constants.ts
|
|
3417
|
-
import { join as
|
|
3418
|
-
import { homedir as
|
|
3419
|
-
var xdgData2 = process.env.XDG_DATA_HOME ||
|
|
3420
|
-
var OPENCODE_STORAGE2 =
|
|
3421
|
-
var MESSAGE_STORAGE =
|
|
3422
|
-
var PART_STORAGE =
|
|
3735
|
+
import { join as join10 } from "path";
|
|
3736
|
+
import { homedir as homedir3 } from "os";
|
|
3737
|
+
var xdgData2 = process.env.XDG_DATA_HOME || join10(homedir3(), ".local", "share");
|
|
3738
|
+
var OPENCODE_STORAGE2 = join10(xdgData2, "opencode", "storage");
|
|
3739
|
+
var MESSAGE_STORAGE = join10(OPENCODE_STORAGE2, "message");
|
|
3740
|
+
var PART_STORAGE = join10(OPENCODE_STORAGE2, "part");
|
|
3423
3741
|
var THINKING_TYPES = new Set(["thinking", "redacted_thinking", "reasoning"]);
|
|
3424
3742
|
var META_TYPES = new Set(["step-start", "step-finish"]);
|
|
3425
3743
|
var CONTENT_TYPES = new Set(["text", "tool", "tool_use", "tool_result"]);
|
|
@@ -3427,10 +3745,10 @@ var CONTENT_TYPES = new Set(["text", "tool", "tool_use", "tool_result"]);
|
|
|
3427
3745
|
// src/shared-hooks/utils/message-storage.ts
|
|
3428
3746
|
function findNearestMessageWithFields(messageDir) {
|
|
3429
3747
|
try {
|
|
3430
|
-
const files =
|
|
3748
|
+
const files = readdirSync2(messageDir).filter((f) => f.endsWith(".json")).sort().reverse();
|
|
3431
3749
|
for (const file of files) {
|
|
3432
3750
|
try {
|
|
3433
|
-
const content = readFileSync7(
|
|
3751
|
+
const content = readFileSync7(join11(messageDir, file), "utf-8");
|
|
3434
3752
|
const msg = JSON.parse(content);
|
|
3435
3753
|
if (msg.agent && msg.model?.providerID && msg.model?.modelID) {
|
|
3436
3754
|
return msg;
|
|
@@ -3448,8 +3766,8 @@ function findNearestMessageWithFields(messageDir) {
|
|
|
3448
3766
|
// src/shared-hooks/utils/logger.ts
|
|
3449
3767
|
import * as fs2 from "fs";
|
|
3450
3768
|
import * as os2 from "os";
|
|
3451
|
-
import * as
|
|
3452
|
-
var logFile =
|
|
3769
|
+
import * as path6 from "path";
|
|
3770
|
+
var logFile = path6.join(os2.tmpdir(), "thoth-plugin.log");
|
|
3453
3771
|
function log2(message, data) {
|
|
3454
3772
|
try {
|
|
3455
3773
|
const timestamp = new Date().toISOString();
|
|
@@ -3471,11 +3789,11 @@ Incomplete tasks remain in your todo list. Continue working on the next pending
|
|
|
3471
3789
|
function getMessageDir(sessionID) {
|
|
3472
3790
|
if (!existsSync8(MESSAGE_STORAGE))
|
|
3473
3791
|
return null;
|
|
3474
|
-
const directPath =
|
|
3792
|
+
const directPath = join13(MESSAGE_STORAGE, sessionID);
|
|
3475
3793
|
if (existsSync8(directPath))
|
|
3476
3794
|
return directPath;
|
|
3477
|
-
for (const dir of
|
|
3478
|
-
const sessionPath =
|
|
3795
|
+
for (const dir of readdirSync3(MESSAGE_STORAGE)) {
|
|
3796
|
+
const sessionPath = join13(MESSAGE_STORAGE, dir, sessionID);
|
|
3479
3797
|
if (existsSync8(sessionPath))
|
|
3480
3798
|
return sessionPath;
|
|
3481
3799
|
}
|
|
@@ -3703,16 +4021,16 @@ function createTodoContinuationEnforcer(ctx) {
|
|
|
3703
4021
|
};
|
|
3704
4022
|
}
|
|
3705
4023
|
// src/shared-hooks/session-recovery/storage.ts
|
|
3706
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync as
|
|
3707
|
-
import { join as
|
|
4024
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readdirSync as readdirSync4, readFileSync as readFileSync8, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
4025
|
+
import { join as join15 } from "path";
|
|
3708
4026
|
|
|
3709
4027
|
// src/shared-hooks/session-recovery/constants.ts
|
|
3710
|
-
import { join as
|
|
3711
|
-
import { homedir as
|
|
3712
|
-
var xdgData3 = process.env.XDG_DATA_HOME ||
|
|
3713
|
-
var OPENCODE_STORAGE3 =
|
|
3714
|
-
var MESSAGE_STORAGE2 =
|
|
3715
|
-
var PART_STORAGE2 =
|
|
4028
|
+
import { join as join14 } from "path";
|
|
4029
|
+
import { homedir as homedir4 } from "os";
|
|
4030
|
+
var xdgData3 = process.env.XDG_DATA_HOME || join14(homedir4(), ".local", "share");
|
|
4031
|
+
var OPENCODE_STORAGE3 = join14(xdgData3, "opencode", "storage");
|
|
4032
|
+
var MESSAGE_STORAGE2 = join14(OPENCODE_STORAGE3, "message");
|
|
4033
|
+
var PART_STORAGE2 = join14(OPENCODE_STORAGE3, "part");
|
|
3716
4034
|
var THINKING_TYPES2 = new Set(["thinking", "redacted_thinking", "reasoning"]);
|
|
3717
4035
|
var META_TYPES2 = new Set(["step-start", "step-finish"]);
|
|
3718
4036
|
var CONTENT_TYPES2 = new Set(["text", "tool", "tool_use", "tool_result"]);
|
|
@@ -3721,12 +4039,12 @@ var CONTENT_TYPES2 = new Set(["text", "tool", "tool_use", "tool_result"]);
|
|
|
3721
4039
|
function getMessageDir2(sessionID) {
|
|
3722
4040
|
if (!existsSync9(MESSAGE_STORAGE2))
|
|
3723
4041
|
return "";
|
|
3724
|
-
const directPath =
|
|
4042
|
+
const directPath = join15(MESSAGE_STORAGE2, sessionID);
|
|
3725
4043
|
if (existsSync9(directPath)) {
|
|
3726
4044
|
return directPath;
|
|
3727
4045
|
}
|
|
3728
|
-
for (const dir of
|
|
3729
|
-
const sessionPath =
|
|
4046
|
+
for (const dir of readdirSync4(MESSAGE_STORAGE2)) {
|
|
4047
|
+
const sessionPath = join15(MESSAGE_STORAGE2, dir, sessionID);
|
|
3730
4048
|
if (existsSync9(sessionPath)) {
|
|
3731
4049
|
return sessionPath;
|
|
3732
4050
|
}
|
|
@@ -3738,11 +4056,11 @@ function readMessages(sessionID) {
|
|
|
3738
4056
|
if (!messageDir || !existsSync9(messageDir))
|
|
3739
4057
|
return [];
|
|
3740
4058
|
const messages = [];
|
|
3741
|
-
for (const file of
|
|
4059
|
+
for (const file of readdirSync4(messageDir)) {
|
|
3742
4060
|
if (!file.endsWith(".json"))
|
|
3743
4061
|
continue;
|
|
3744
4062
|
try {
|
|
3745
|
-
const content = readFileSync8(
|
|
4063
|
+
const content = readFileSync8(join15(messageDir, file), "utf-8");
|
|
3746
4064
|
messages.push(JSON.parse(content));
|
|
3747
4065
|
} catch {
|
|
3748
4066
|
continue;
|
|
@@ -3757,15 +4075,15 @@ function readMessages(sessionID) {
|
|
|
3757
4075
|
});
|
|
3758
4076
|
}
|
|
3759
4077
|
function readParts(messageID) {
|
|
3760
|
-
const partDir =
|
|
4078
|
+
const partDir = join15(PART_STORAGE2, messageID);
|
|
3761
4079
|
if (!existsSync9(partDir))
|
|
3762
4080
|
return [];
|
|
3763
4081
|
const parts = [];
|
|
3764
|
-
for (const file of
|
|
4082
|
+
for (const file of readdirSync4(partDir)) {
|
|
3765
4083
|
if (!file.endsWith(".json"))
|
|
3766
4084
|
continue;
|
|
3767
4085
|
try {
|
|
3768
|
-
const content = readFileSync8(
|
|
4086
|
+
const content = readFileSync8(join15(partDir, file), "utf-8");
|
|
3769
4087
|
parts.push(JSON.parse(content));
|
|
3770
4088
|
} catch {
|
|
3771
4089
|
continue;
|
|
@@ -3807,7 +4125,7 @@ function findMessagesWithOrphanThinking(sessionID) {
|
|
|
3807
4125
|
return result;
|
|
3808
4126
|
}
|
|
3809
4127
|
function prependThinkingPart(sessionID, messageID) {
|
|
3810
|
-
const partDir =
|
|
4128
|
+
const partDir = join15(PART_STORAGE2, messageID);
|
|
3811
4129
|
if (!existsSync9(partDir)) {
|
|
3812
4130
|
mkdirSync4(partDir, { recursive: true });
|
|
3813
4131
|
}
|
|
@@ -3821,22 +4139,22 @@ function prependThinkingPart(sessionID, messageID) {
|
|
|
3821
4139
|
synthetic: true
|
|
3822
4140
|
};
|
|
3823
4141
|
try {
|
|
3824
|
-
writeFileSync5(
|
|
4142
|
+
writeFileSync5(join15(partDir, `${partId}.json`), JSON.stringify(part, null, 2));
|
|
3825
4143
|
return true;
|
|
3826
4144
|
} catch {
|
|
3827
4145
|
return false;
|
|
3828
4146
|
}
|
|
3829
4147
|
}
|
|
3830
4148
|
function stripThinkingParts(messageID) {
|
|
3831
|
-
const partDir =
|
|
4149
|
+
const partDir = join15(PART_STORAGE2, messageID);
|
|
3832
4150
|
if (!existsSync9(partDir))
|
|
3833
4151
|
return false;
|
|
3834
4152
|
let anyRemoved = false;
|
|
3835
|
-
for (const file of
|
|
4153
|
+
for (const file of readdirSync4(partDir)) {
|
|
3836
4154
|
if (!file.endsWith(".json"))
|
|
3837
4155
|
continue;
|
|
3838
4156
|
try {
|
|
3839
|
-
const filePath =
|
|
4157
|
+
const filePath = join15(partDir, file);
|
|
3840
4158
|
const content = readFileSync8(filePath, "utf-8");
|
|
3841
4159
|
const part = JSON.parse(content);
|
|
3842
4160
|
if (THINKING_TYPES2.has(part.type)) {
|
|
@@ -4163,16 +4481,16 @@ ${CONTEXT_REMINDER}
|
|
|
4163
4481
|
};
|
|
4164
4482
|
}
|
|
4165
4483
|
// src/shared-hooks/background-agent/manager.ts
|
|
4166
|
-
import { existsSync as existsSync10, readdirSync as
|
|
4167
|
-
import { join as
|
|
4484
|
+
import { existsSync as existsSync10, readdirSync as readdirSync5 } from "fs";
|
|
4485
|
+
import { join as join16 } from "path";
|
|
4168
4486
|
function getMessageDir3(sessionID) {
|
|
4169
4487
|
if (!existsSync10(MESSAGE_STORAGE))
|
|
4170
4488
|
return null;
|
|
4171
|
-
const directPath =
|
|
4489
|
+
const directPath = join16(MESSAGE_STORAGE, sessionID);
|
|
4172
4490
|
if (existsSync10(directPath))
|
|
4173
4491
|
return directPath;
|
|
4174
|
-
for (const dir of
|
|
4175
|
-
const sessionPath =
|
|
4492
|
+
for (const dir of readdirSync5(MESSAGE_STORAGE)) {
|
|
4493
|
+
const sessionPath = join16(MESSAGE_STORAGE, dir, sessionID);
|
|
4176
4494
|
if (existsSync10(sessionPath))
|
|
4177
4495
|
return sessionPath;
|
|
4178
4496
|
}
|
|
@@ -5249,10 +5567,10 @@ function mergeDefs(...defs) {
|
|
|
5249
5567
|
function cloneDef(schema) {
|
|
5250
5568
|
return mergeDefs(schema._zod.def);
|
|
5251
5569
|
}
|
|
5252
|
-
function getElementAtPath(obj,
|
|
5253
|
-
if (!
|
|
5570
|
+
function getElementAtPath(obj, path7) {
|
|
5571
|
+
if (!path7)
|
|
5254
5572
|
return obj;
|
|
5255
|
-
return
|
|
5573
|
+
return path7.reduce((acc, key) => acc?.[key], obj);
|
|
5256
5574
|
}
|
|
5257
5575
|
function promiseAllObject(promisesObj) {
|
|
5258
5576
|
const keys = Object.keys(promisesObj);
|
|
@@ -5611,11 +5929,11 @@ function aborted(x, startIndex = 0) {
|
|
|
5611
5929
|
}
|
|
5612
5930
|
return false;
|
|
5613
5931
|
}
|
|
5614
|
-
function prefixIssues(
|
|
5932
|
+
function prefixIssues(path7, issues) {
|
|
5615
5933
|
return issues.map((iss) => {
|
|
5616
5934
|
var _a;
|
|
5617
5935
|
(_a = iss).path ?? (_a.path = []);
|
|
5618
|
-
iss.path.unshift(
|
|
5936
|
+
iss.path.unshift(path7);
|
|
5619
5937
|
return iss;
|
|
5620
5938
|
});
|
|
5621
5939
|
}
|
|
@@ -5783,7 +6101,7 @@ function treeifyError(error, _mapper) {
|
|
|
5783
6101
|
return issue2.message;
|
|
5784
6102
|
};
|
|
5785
6103
|
const result = { errors: [] };
|
|
5786
|
-
const processError = (error2,
|
|
6104
|
+
const processError = (error2, path7 = []) => {
|
|
5787
6105
|
var _a, _b;
|
|
5788
6106
|
for (const issue2 of error2.issues) {
|
|
5789
6107
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
@@ -5793,7 +6111,7 @@ function treeifyError(error, _mapper) {
|
|
|
5793
6111
|
} else if (issue2.code === "invalid_element") {
|
|
5794
6112
|
processError({ issues: issue2.issues }, issue2.path);
|
|
5795
6113
|
} else {
|
|
5796
|
-
const fullpath = [...
|
|
6114
|
+
const fullpath = [...path7, ...issue2.path];
|
|
5797
6115
|
if (fullpath.length === 0) {
|
|
5798
6116
|
result.errors.push(mapper(issue2));
|
|
5799
6117
|
continue;
|
|
@@ -5825,8 +6143,8 @@ function treeifyError(error, _mapper) {
|
|
|
5825
6143
|
}
|
|
5826
6144
|
function toDotPath(_path) {
|
|
5827
6145
|
const segs = [];
|
|
5828
|
-
const
|
|
5829
|
-
for (const seg of
|
|
6146
|
+
const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
6147
|
+
for (const seg of path7) {
|
|
5830
6148
|
if (typeof seg === "number")
|
|
5831
6149
|
segs.push(`[${seg}]`);
|
|
5832
6150
|
else if (typeof seg === "symbol")
|
|
@@ -16901,7 +17219,7 @@ Use \`background_output\` tool with task_id="${task.id}" to check progress:
|
|
|
16901
17219
|
});
|
|
16902
17220
|
}
|
|
16903
17221
|
function delay(ms) {
|
|
16904
|
-
return new Promise((
|
|
17222
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
16905
17223
|
}
|
|
16906
17224
|
function truncateText(text, maxLength) {
|
|
16907
17225
|
if (text.length <= maxLength)
|
|
@@ -17115,13 +17433,13 @@ Status: ${task.status}`;
|
|
|
17115
17433
|
});
|
|
17116
17434
|
}
|
|
17117
17435
|
// src/tools/skill/tools.ts
|
|
17118
|
-
import { existsSync as existsSync11, readdirSync as
|
|
17119
|
-
import { homedir as
|
|
17120
|
-
import { join as
|
|
17436
|
+
import { existsSync as existsSync11, readdirSync as readdirSync6, readFileSync as readFileSync9, lstatSync, readlinkSync } from "fs";
|
|
17437
|
+
import { homedir as homedir5 } from "os";
|
|
17438
|
+
import { join as join17, basename as basename3, resolve as resolve3, dirname as dirname5 } from "path";
|
|
17121
17439
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
17122
17440
|
var __filename3 = fileURLToPath2(import.meta.url);
|
|
17123
17441
|
var __dirname3 = dirname5(__filename3);
|
|
17124
|
-
var NPM_DEFAULTS_SKILL_DIR =
|
|
17442
|
+
var NPM_DEFAULTS_SKILL_DIR = join17(__dirname3, "..", "..", "defaults", "skill");
|
|
17125
17443
|
function deduplicateSkills(skills) {
|
|
17126
17444
|
const seen = new Set;
|
|
17127
17445
|
return skills.filter((skill) => {
|
|
@@ -17181,7 +17499,7 @@ function resolveSymlink(filePath) {
|
|
|
17181
17499
|
try {
|
|
17182
17500
|
const stats = lstatSync(filePath, { throwIfNoEntry: false });
|
|
17183
17501
|
if (stats?.isSymbolicLink()) {
|
|
17184
|
-
return
|
|
17502
|
+
return resolve3(filePath, "..", readlinkSync(filePath));
|
|
17185
17503
|
}
|
|
17186
17504
|
return filePath;
|
|
17187
17505
|
} catch {
|
|
@@ -17194,6 +17512,7 @@ function parseSkillFrontmatter(data) {
|
|
|
17194
17512
|
description: typeof data.description === "string" ? data.description : "",
|
|
17195
17513
|
license: typeof data.license === "string" ? data.license : undefined,
|
|
17196
17514
|
"allowed-tools": Array.isArray(data["allowed-tools"]) ? data["allowed-tools"] : undefined,
|
|
17515
|
+
triggers: Array.isArray(data.triggers) ? data.triggers : undefined,
|
|
17197
17516
|
metadata: typeof data.metadata === "object" && data.metadata !== null ? data.metadata : undefined
|
|
17198
17517
|
};
|
|
17199
17518
|
}
|
|
@@ -17201,15 +17520,15 @@ function discoverSkillsFromDir(skillsDir, scope) {
|
|
|
17201
17520
|
if (!existsSync11(skillsDir)) {
|
|
17202
17521
|
return [];
|
|
17203
17522
|
}
|
|
17204
|
-
const entries =
|
|
17523
|
+
const entries = readdirSync6(skillsDir, { withFileTypes: true });
|
|
17205
17524
|
const skills = [];
|
|
17206
17525
|
for (const entry of entries) {
|
|
17207
17526
|
if (entry.name.startsWith("."))
|
|
17208
17527
|
continue;
|
|
17209
|
-
const skillPath =
|
|
17528
|
+
const skillPath = join17(skillsDir, entry.name);
|
|
17210
17529
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
17211
17530
|
const resolvedPath = resolveSymlink(skillPath);
|
|
17212
|
-
const skillMdPath =
|
|
17531
|
+
const skillMdPath = join17(resolvedPath, "SKILL.md");
|
|
17213
17532
|
if (!existsSync11(skillMdPath))
|
|
17214
17533
|
continue;
|
|
17215
17534
|
try {
|
|
@@ -17218,7 +17537,8 @@ function discoverSkillsFromDir(skillsDir, scope) {
|
|
|
17218
17537
|
skills.push({
|
|
17219
17538
|
name: data.name || entry.name,
|
|
17220
17539
|
description: data.description || "",
|
|
17221
|
-
scope
|
|
17540
|
+
scope,
|
|
17541
|
+
triggers: Array.isArray(data.triggers) ? data.triggers : undefined
|
|
17222
17542
|
});
|
|
17223
17543
|
} catch {
|
|
17224
17544
|
continue;
|
|
@@ -17228,16 +17548,35 @@ function discoverSkillsFromDir(skillsDir, scope) {
|
|
|
17228
17548
|
return skills;
|
|
17229
17549
|
}
|
|
17230
17550
|
function discoverSkillsSync() {
|
|
17231
|
-
const userSkillsDir =
|
|
17232
|
-
const projectSkillsDir =
|
|
17551
|
+
const userSkillsDir = join17(homedir5(), ".opencode", "skill");
|
|
17552
|
+
const projectSkillsDir = join17(process.cwd(), ".opencode", "skill");
|
|
17233
17553
|
const builtinSkills = discoverSkillsFromDir(NPM_DEFAULTS_SKILL_DIR, "builtin");
|
|
17234
17554
|
const userSkills = discoverSkillsFromDir(userSkillsDir, "user");
|
|
17235
17555
|
const projectSkills = discoverSkillsFromDir(projectSkillsDir, "project");
|
|
17236
17556
|
return deduplicateSkills([...projectSkills, ...userSkills, ...builtinSkills]);
|
|
17237
17557
|
}
|
|
17558
|
+
function buildTriggerSection(skills) {
|
|
17559
|
+
const skillsWithTriggers = skills.filter((s) => s.triggers && s.triggers.length > 0);
|
|
17560
|
+
if (skillsWithTriggers.length === 0) {
|
|
17561
|
+
return "";
|
|
17562
|
+
}
|
|
17563
|
+
const lines = [
|
|
17564
|
+
`
|
|
17565
|
+
|
|
17566
|
+
Skill Triggers (invoke skill when user says these phrases):`
|
|
17567
|
+
];
|
|
17568
|
+
for (const skill of skillsWithTriggers) {
|
|
17569
|
+
const triggers = skill.triggers.map((t) => `"${t}"`).join(", ");
|
|
17570
|
+
lines.push(`- ${triggers} \u2192 ${skill.name}`);
|
|
17571
|
+
}
|
|
17572
|
+
lines.push(`
|
|
17573
|
+
IMPORTANT: If user intent matches a trigger phrase, invoke the skill immediately. Don't improvise.`);
|
|
17574
|
+
return lines.join(`
|
|
17575
|
+
`);
|
|
17576
|
+
}
|
|
17238
17577
|
async function parseSkillMd(skillPath) {
|
|
17239
17578
|
const resolvedPath = resolveSymlink(skillPath);
|
|
17240
|
-
const skillMdPath =
|
|
17579
|
+
const skillMdPath = join17(resolvedPath, "SKILL.md");
|
|
17241
17580
|
if (!existsSync11(skillMdPath)) {
|
|
17242
17581
|
return null;
|
|
17243
17582
|
}
|
|
@@ -17246,18 +17585,19 @@ async function parseSkillMd(skillPath) {
|
|
|
17246
17585
|
const { data, body } = parseFrontmatter(content);
|
|
17247
17586
|
const frontmatter = parseSkillFrontmatter(data);
|
|
17248
17587
|
const metadata = {
|
|
17249
|
-
name: frontmatter.name ||
|
|
17588
|
+
name: frontmatter.name || basename3(skillPath),
|
|
17250
17589
|
description: frontmatter.description,
|
|
17251
17590
|
license: frontmatter.license,
|
|
17252
17591
|
allowedTools: frontmatter["allowed-tools"],
|
|
17592
|
+
triggers: frontmatter.triggers,
|
|
17253
17593
|
metadata: frontmatter.metadata
|
|
17254
17594
|
};
|
|
17255
|
-
const referencesDir =
|
|
17256
|
-
const scriptsDir =
|
|
17257
|
-
const assetsDir =
|
|
17258
|
-
const references = existsSync11(referencesDir) ?
|
|
17259
|
-
const scripts = existsSync11(scriptsDir) ?
|
|
17260
|
-
const assets = existsSync11(assetsDir) ?
|
|
17595
|
+
const referencesDir = join17(resolvedPath, "references");
|
|
17596
|
+
const scriptsDir = join17(resolvedPath, "scripts");
|
|
17597
|
+
const assetsDir = join17(resolvedPath, "assets");
|
|
17598
|
+
const references = existsSync11(referencesDir) ? readdirSync6(referencesDir).filter((f) => !f.startsWith(".")) : [];
|
|
17599
|
+
const scripts = existsSync11(scriptsDir) ? readdirSync6(scriptsDir).filter((f) => !f.startsWith(".") && !f.startsWith("__")) : [];
|
|
17600
|
+
const assets = existsSync11(assetsDir) ? readdirSync6(assetsDir).filter((f) => !f.startsWith(".")) : [];
|
|
17261
17601
|
return {
|
|
17262
17602
|
name: metadata.name,
|
|
17263
17603
|
path: resolvedPath,
|
|
@@ -17276,12 +17616,12 @@ async function discoverSkillsFromDirAsync(skillsDir) {
|
|
|
17276
17616
|
if (!existsSync11(skillsDir)) {
|
|
17277
17617
|
return [];
|
|
17278
17618
|
}
|
|
17279
|
-
const entries =
|
|
17619
|
+
const entries = readdirSync6(skillsDir, { withFileTypes: true });
|
|
17280
17620
|
const skills = [];
|
|
17281
17621
|
for (const entry of entries) {
|
|
17282
17622
|
if (entry.name.startsWith("."))
|
|
17283
17623
|
continue;
|
|
17284
|
-
const skillPath =
|
|
17624
|
+
const skillPath = join17(skillsDir, entry.name);
|
|
17285
17625
|
if (entry.isDirectory() || entry.isSymbolicLink()) {
|
|
17286
17626
|
const skillInfo = await parseSkillMd(skillPath);
|
|
17287
17627
|
if (skillInfo) {
|
|
@@ -17292,8 +17632,8 @@ async function discoverSkillsFromDirAsync(skillsDir) {
|
|
|
17292
17632
|
return skills;
|
|
17293
17633
|
}
|
|
17294
17634
|
async function discoverSkills() {
|
|
17295
|
-
const userSkillsDir =
|
|
17296
|
-
const projectSkillsDir =
|
|
17635
|
+
const userSkillsDir = join17(homedir5(), ".opencode", "skill");
|
|
17636
|
+
const projectSkillsDir = join17(process.cwd(), ".opencode", "skill");
|
|
17297
17637
|
const builtinSkills = await discoverSkillsFromDirAsync(NPM_DEFAULTS_SKILL_DIR);
|
|
17298
17638
|
const userSkills = await discoverSkillsFromDirAsync(userSkillsDir);
|
|
17299
17639
|
const projectSkills = await discoverSkillsFromDirAsync(projectSkillsDir);
|
|
@@ -17323,7 +17663,7 @@ async function loadSkillWithReferences(skill, includeRefs) {
|
|
|
17323
17663
|
const referencesLoaded = [];
|
|
17324
17664
|
if (includeRefs && skill.references.length > 0) {
|
|
17325
17665
|
for (const ref of skill.references) {
|
|
17326
|
-
const refPath =
|
|
17666
|
+
const refPath = join17(skill.path, "references", ref);
|
|
17327
17667
|
try {
|
|
17328
17668
|
const content = readFileSync9(refPath, "utf-8");
|
|
17329
17669
|
referencesLoaded.push({ path: ref, content });
|
|
@@ -17384,13 +17724,14 @@ function createSkillTool() {
|
|
|
17384
17724
|
const availableSkills = discoverSkillsSync();
|
|
17385
17725
|
const skillListForDescription = availableSkills.map((s) => `- ${s.name}: ${s.description} (${s.scope})`).join(`
|
|
17386
17726
|
`);
|
|
17727
|
+
const triggerSection = buildTriggerSection(availableSkills);
|
|
17387
17728
|
return tool({
|
|
17388
17729
|
description: `Execute a skill within the main conversation.
|
|
17389
17730
|
|
|
17390
17731
|
When you invoke a skill, the skill's prompt will expand and provide detailed instructions on how to complete the task.
|
|
17391
17732
|
|
|
17392
17733
|
Available Skills:
|
|
17393
|
-
${skillListForDescription || "(No skills discovered yet)"}`,
|
|
17734
|
+
${skillListForDescription || "(No skills discovered yet)"}${triggerSection}`,
|
|
17394
17735
|
args: {
|
|
17395
17736
|
skill: tool.schema.string().describe("The skill name or search query to find and load. Can be exact skill name (e.g., 'morning-boot') or keywords (e.g., 'morning', 'boot').")
|
|
17396
17737
|
},
|
|
@@ -18183,10 +18524,10 @@ function mergeDefs2(...defs) {
|
|
|
18183
18524
|
function cloneDef2(schema) {
|
|
18184
18525
|
return mergeDefs2(schema._zod.def);
|
|
18185
18526
|
}
|
|
18186
|
-
function getElementAtPath2(obj,
|
|
18187
|
-
if (!
|
|
18527
|
+
function getElementAtPath2(obj, path7) {
|
|
18528
|
+
if (!path7)
|
|
18188
18529
|
return obj;
|
|
18189
|
-
return
|
|
18530
|
+
return path7.reduce((acc, key) => acc?.[key], obj);
|
|
18190
18531
|
}
|
|
18191
18532
|
function promiseAllObject2(promisesObj) {
|
|
18192
18533
|
const keys = Object.keys(promisesObj);
|
|
@@ -18567,11 +18908,11 @@ function aborted2(x, startIndex = 0) {
|
|
|
18567
18908
|
}
|
|
18568
18909
|
return false;
|
|
18569
18910
|
}
|
|
18570
|
-
function prefixIssues2(
|
|
18911
|
+
function prefixIssues2(path7, issues) {
|
|
18571
18912
|
return issues.map((iss) => {
|
|
18572
18913
|
var _a;
|
|
18573
18914
|
(_a = iss).path ?? (_a.path = []);
|
|
18574
|
-
iss.path.unshift(
|
|
18915
|
+
iss.path.unshift(path7);
|
|
18575
18916
|
return iss;
|
|
18576
18917
|
});
|
|
18577
18918
|
}
|
|
@@ -18754,7 +19095,7 @@ function formatError2(error45, mapper = (issue3) => issue3.message) {
|
|
|
18754
19095
|
}
|
|
18755
19096
|
function treeifyError2(error45, mapper = (issue3) => issue3.message) {
|
|
18756
19097
|
const result = { errors: [] };
|
|
18757
|
-
const processError = (error46,
|
|
19098
|
+
const processError = (error46, path7 = []) => {
|
|
18758
19099
|
var _a, _b;
|
|
18759
19100
|
for (const issue3 of error46.issues) {
|
|
18760
19101
|
if (issue3.code === "invalid_union" && issue3.errors.length) {
|
|
@@ -18764,7 +19105,7 @@ function treeifyError2(error45, mapper = (issue3) => issue3.message) {
|
|
|
18764
19105
|
} else if (issue3.code === "invalid_element") {
|
|
18765
19106
|
processError({ issues: issue3.issues }, issue3.path);
|
|
18766
19107
|
} else {
|
|
18767
|
-
const fullpath = [...
|
|
19108
|
+
const fullpath = [...path7, ...issue3.path];
|
|
18768
19109
|
if (fullpath.length === 0) {
|
|
18769
19110
|
result.errors.push(mapper(issue3));
|
|
18770
19111
|
continue;
|
|
@@ -18796,8 +19137,8 @@ function treeifyError2(error45, mapper = (issue3) => issue3.message) {
|
|
|
18796
19137
|
}
|
|
18797
19138
|
function toDotPath2(_path) {
|
|
18798
19139
|
const segs = [];
|
|
18799
|
-
const
|
|
18800
|
-
for (const seg of
|
|
19140
|
+
const path7 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
19141
|
+
for (const seg of path7) {
|
|
18801
19142
|
if (typeof seg === "number")
|
|
18802
19143
|
segs.push(`[${seg}]`);
|
|
18803
19144
|
else if (typeof seg === "symbol")
|
|
@@ -30544,13 +30885,13 @@ function resolveRef(ref, ctx) {
|
|
|
30544
30885
|
if (!ref.startsWith("#")) {
|
|
30545
30886
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
30546
30887
|
}
|
|
30547
|
-
const
|
|
30548
|
-
if (
|
|
30888
|
+
const path7 = ref.slice(1).split("/").filter(Boolean);
|
|
30889
|
+
if (path7.length === 0) {
|
|
30549
30890
|
return ctx.rootSchema;
|
|
30550
30891
|
}
|
|
30551
30892
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
30552
|
-
if (
|
|
30553
|
-
const key =
|
|
30893
|
+
if (path7[0] === defsKey) {
|
|
30894
|
+
const key = path7[1];
|
|
30554
30895
|
if (!key || !ctx.defs[key]) {
|
|
30555
30896
|
throw new Error(`Reference not found: ${ref}`);
|
|
30556
30897
|
}
|
|
@@ -30964,6 +31305,7 @@ var HooksConfigSchema = exports_external2.object({
|
|
|
30964
31305
|
"temporal-awareness": exports_external2.boolean().optional(),
|
|
30965
31306
|
"knowledge-persistence": exports_external2.boolean().optional(),
|
|
30966
31307
|
"directory-agents-injector": exports_external2.boolean().optional(),
|
|
31308
|
+
"frontmatter-enforcer": exports_external2.boolean().optional(),
|
|
30967
31309
|
"todo-continuation": exports_external2.boolean().optional(),
|
|
30968
31310
|
"session-recovery": exports_external2.boolean().optional(),
|
|
30969
31311
|
"context-window-monitor": exports_external2.boolean().optional(),
|
|
@@ -31001,7 +31343,7 @@ var ThothPluginConfigSchema = exports_external2.object({
|
|
|
31001
31343
|
}).strict();
|
|
31002
31344
|
// src/index.ts
|
|
31003
31345
|
import * as fs3 from "fs";
|
|
31004
|
-
import * as
|
|
31346
|
+
import * as path7 from "path";
|
|
31005
31347
|
var sessionSpecializations = new Map;
|
|
31006
31348
|
function loadConfigFromPath(configPath) {
|
|
31007
31349
|
try {
|
|
@@ -31033,8 +31375,8 @@ function mergeConfigs(base, override) {
|
|
|
31033
31375
|
};
|
|
31034
31376
|
}
|
|
31035
31377
|
function loadPluginConfig(directory) {
|
|
31036
|
-
const userConfigPath =
|
|
31037
|
-
const projectConfigPath =
|
|
31378
|
+
const userConfigPath = path7.join(getUserConfigDir(), "opencode", "thoth-plugin.json");
|
|
31379
|
+
const projectConfigPath = path7.join(directory, ".opencode", "thoth-plugin.json");
|
|
31038
31380
|
let config3 = loadConfigFromPath(userConfigPath) ?? {};
|
|
31039
31381
|
const projectConfig = loadConfigFromPath(projectConfigPath);
|
|
31040
31382
|
if (projectConfig) {
|
|
@@ -31048,15 +31390,15 @@ function resolveKnowledgeBasePath(config3, directory) {
|
|
|
31048
31390
|
return expandPath(config3.knowledge_base);
|
|
31049
31391
|
}
|
|
31050
31392
|
const commonLocations = [
|
|
31051
|
-
|
|
31052
|
-
|
|
31053
|
-
|
|
31054
|
-
|
|
31055
|
-
|
|
31056
|
-
|
|
31393
|
+
path7.join(process.env.HOME || "", "Repos", "thoth"),
|
|
31394
|
+
path7.join(process.env.HOME || "", "repos", "thoth"),
|
|
31395
|
+
path7.join(process.env.HOME || "", "Projects", "thoth"),
|
|
31396
|
+
path7.join(process.env.HOME || "", "projects", "thoth"),
|
|
31397
|
+
path7.join(process.env.HOME || "", "thoth"),
|
|
31398
|
+
path7.join(directory, "thoth")
|
|
31057
31399
|
];
|
|
31058
31400
|
for (const location of commonLocations) {
|
|
31059
|
-
const kernelPath =
|
|
31401
|
+
const kernelPath = path7.join(location, "kernel");
|
|
31060
31402
|
if (fs3.existsSync(kernelPath)) {
|
|
31061
31403
|
log(`Found knowledge base at: ${location}`);
|
|
31062
31404
|
return location;
|
|
@@ -31077,6 +31419,7 @@ var ThothPlugin = async (ctx) => {
|
|
|
31077
31419
|
const trustLevelTracker = hooksConfig["trust-level-tracker"] !== false ? createTrustLevelTrackerHook({ knowledgeBasePath }) : null;
|
|
31078
31420
|
const contextAperture = hooksConfig["context-aperture"] !== false ? createContextApertureHook({ knowledgeBasePath }) : null;
|
|
31079
31421
|
const temporalAwareness = hooksConfig["temporal-awareness"] !== false ? createTemporalAwarenessHook() : null;
|
|
31422
|
+
const frontmatterEnforcer = hooksConfig["frontmatter-enforcer"] !== false ? createFrontmatterEnforcerHook({ knowledgeBasePath }) : null;
|
|
31080
31423
|
const todoContinuationEnforcer = hooksConfig["todo-continuation"] !== false ? createTodoContinuationEnforcer(ctx) : null;
|
|
31081
31424
|
const sessionRecoveryHook = hooksConfig["session-recovery"] !== false ? createSessionRecoveryHook(ctx, { experimental: { auto_resume: true } }) : null;
|
|
31082
31425
|
const contextWindowMonitor = hooksConfig["context-window-monitor"] !== false ? createContextWindowMonitorHook(ctx) : null;
|
|
@@ -31181,10 +31524,12 @@ var ThothPlugin = async (ctx) => {
|
|
|
31181
31524
|
await temporalAwareness?.["tool.execute.before"]?.(input, output);
|
|
31182
31525
|
await contextAperture?.["tool.execute.before"]?.(input, output);
|
|
31183
31526
|
await trustLevelTracker?.["tool.execute.before"]?.(input, output);
|
|
31527
|
+
await frontmatterEnforcer?.["tool.execute.before"]?.(input, output);
|
|
31184
31528
|
},
|
|
31185
31529
|
"tool.execute.after": async (input, output) => {
|
|
31186
31530
|
await trustLevelTracker?.["tool.execute.after"]?.(input, output);
|
|
31187
31531
|
await contextAperture?.["tool.execute.after"]?.(input, output);
|
|
31532
|
+
await frontmatterEnforcer?.["tool.execute.after"]?.(input, output);
|
|
31188
31533
|
await directoryAgentsInjector?.["tool.execute.after"]?.(input, output);
|
|
31189
31534
|
await contextWindowMonitor?.["tool.execute.after"]?.(input, output);
|
|
31190
31535
|
}
|