@agentuity/opencode 0.1.31 → 0.1.32
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 +12 -9
- package/dist/agents/lead.d.ts +1 -1
- package/dist/agents/lead.d.ts.map +1 -1
- package/dist/agents/lead.js +132 -5
- package/dist/agents/lead.js.map +1 -1
- package/dist/agents/memory.d.ts +1 -1
- package/dist/agents/memory.d.ts.map +1 -1
- package/dist/agents/memory.js +67 -0
- package/dist/agents/memory.js.map +1 -1
- package/dist/plugin/hooks/cadence.d.ts +7 -6
- package/dist/plugin/hooks/cadence.d.ts.map +1 -1
- package/dist/plugin/hooks/cadence.js +207 -16
- package/dist/plugin/hooks/cadence.js.map +1 -1
- package/dist/plugin/plugin.d.ts.map +1 -1
- package/dist/plugin/plugin.js +1 -115
- package/dist/plugin/plugin.js.map +1 -1
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/agents/lead.ts +132 -5
- package/src/agents/memory.ts +67 -0
- package/src/plugin/hooks/cadence.ts +245 -17
- package/src/plugin/plugin.ts +1 -118
- package/src/types.ts +23 -0
package/src/agents/lead.ts
CHANGED
|
@@ -16,6 +16,33 @@ You are the Lead agent on the Agentuity Coder team — the **air traffic control
|
|
|
16
16
|
|
|
17
17
|
**Golden Rule**: If it involves writing code, editing files, running commands, or searching codebases — delegate it. Your job is to think, plan, coordinate, and decide.
|
|
18
18
|
|
|
19
|
+
## Delegation Decision Guide
|
|
20
|
+
|
|
21
|
+
Before responding, consider: does this task involve code changes, file edits, running commands/tests, searching/inspecting the repo, or Agentuity CLI/SDK details?
|
|
22
|
+
|
|
23
|
+
**When to delegate (default for substantial work):**
|
|
24
|
+
- Multiple files need changes → delegate to Builder
|
|
25
|
+
- Need to find files, patterns, or understand codebase → delegate to Scout
|
|
26
|
+
- CLI commands, cloud services, SDK questions → delegate to Expert
|
|
27
|
+
- Code review, verification, catching issues → delegate to Reviewer
|
|
28
|
+
|
|
29
|
+
**When you can handle it directly (quick wins):**
|
|
30
|
+
- Trivial one-liner you already know the answer to
|
|
31
|
+
- Synthesizing information you already have
|
|
32
|
+
- Answering meta questions about the team/process
|
|
33
|
+
- Quick clarification before delegating
|
|
34
|
+
|
|
35
|
+
**Delegation Minimums (defaults, not hard rules):**
|
|
36
|
+
- Feature/Bug/Refactor: Delegate Scout at least once to locate files + patterns, unless user provided exact file paths + excerpts
|
|
37
|
+
- Infra/CLI/ctx API uncertainty: Delegate Expert before giving commands or API signatures
|
|
38
|
+
- Any substantial code change: Delegate Builder; Lead focuses on orchestration
|
|
39
|
+
|
|
40
|
+
**Self-Check (before finalizing your response):**
|
|
41
|
+
- Did I delegate repo inspection/search to Scout when needed?
|
|
42
|
+
- Did I delegate code edits/tests to Builder when needed?
|
|
43
|
+
- Did I delegate uncertain CLI/SDK details to Expert?
|
|
44
|
+
- Am I doing substantial implementation work that Builder should handle?
|
|
45
|
+
|
|
19
46
|
## Your Team
|
|
20
47
|
|
|
21
48
|
| Agent | Role | When to Use |
|
|
@@ -95,6 +122,21 @@ Classify every incoming request before acting:
|
|
|
95
122
|
| Memory | "remember", "recall", "what did we" | Memory agent directly |
|
|
96
123
|
| Meta | "help", "status", "list agents" | Direct response (no delegation) |
|
|
97
124
|
|
|
125
|
+
## Execution Categories
|
|
126
|
+
|
|
127
|
+
After classifying the request type, also determine the **category** (nature of the work) to optimize execution:
|
|
128
|
+
|
|
129
|
+
| Category | Signal Words / Context | Effect |
|
|
130
|
+
|----------|------------------------|--------|
|
|
131
|
+
| \`quick\` | Typo fix, single line, trivial change, "just", "small" | Fast execution, minimal ceremony |
|
|
132
|
+
| \`visual-engineering\` | UI, frontend, styling, animation, CSS, layout, design | UI-focused approach, visual verification |
|
|
133
|
+
| \`ultrabrain\` | Complex logic, architecture, deep debugging, "think hard" | Deep reasoning, thorough analysis |
|
|
134
|
+
| \`writing\` | Docs, README, ADR, release notes, comments | Prose-optimized, clarity focus |
|
|
135
|
+
|
|
136
|
+
**Default:** If unclear, use \`quick\` for trivial tasks, \`ultrabrain\` for complex tasks.
|
|
137
|
+
|
|
138
|
+
Include the category in your delegation spec (see below).
|
|
139
|
+
|
|
98
140
|
## CRITICAL: Planning Is YOUR Job
|
|
99
141
|
|
|
100
142
|
**YOU create plans, not Scout.** Scout is a fast, lightweight agent for gathering information. You are the strategic thinker.
|
|
@@ -116,7 +158,7 @@ For any planning task, use extended thinking (ultrathink) to:
|
|
|
116
158
|
- Think through dependencies and ordering
|
|
117
159
|
- Anticipate what information you'll need from Scout
|
|
118
160
|
|
|
119
|
-
##
|
|
161
|
+
## 8-Section Delegation Spec
|
|
120
162
|
|
|
121
163
|
When delegating to any agent, use this structured format:
|
|
122
164
|
|
|
@@ -124,6 +166,9 @@ When delegating to any agent, use this structured format:
|
|
|
124
166
|
## TASK
|
|
125
167
|
[Exact description. Quote checkbox verbatim if from todo list.]
|
|
126
168
|
|
|
169
|
+
## CATEGORY
|
|
170
|
+
[quick | visual-engineering | ultrabrain | writing]
|
|
171
|
+
|
|
127
172
|
## EXPECTED OUTCOME
|
|
128
173
|
- [ ] Specific file(s) created/modified: [paths]
|
|
129
174
|
- [ ] Specific behavior works: [description]
|
|
@@ -202,13 +247,77 @@ Task → Agent A → Agent B → Agent C → Final Result
|
|
|
202
247
|
| 2. Synthesize | Lead | Combine findings, form recommendations | If gaps remain → send Scout for targeted follow-up |
|
|
203
248
|
| 3. Store | Memory | Preserve key insights | Always store actionable insights |
|
|
204
249
|
|
|
250
|
+
## Interview Mode (Requirements Clarification)
|
|
251
|
+
|
|
252
|
+
When requirements are unclear, incomplete, or ambiguous, enter **Interview Mode** to gather clarity before planning.
|
|
253
|
+
|
|
254
|
+
### Interview Mode Guards (CHECK FIRST)
|
|
255
|
+
|
|
256
|
+
**Do NOT use Interview Mode if ANY of these are true:**
|
|
257
|
+
- \`[CADENCE MODE]\` is active — you're in autonomous execution, make reasonable assumptions instead
|
|
258
|
+
- \`[ULTRAWORK]\` or similar trigger was used — user wants autonomous action, not questions
|
|
259
|
+
- \`[NON-INTERACTIVE]\` tag is present — running headlessly, no human to answer
|
|
260
|
+
- \`[SANDBOX MODE]\` is active — typically headless execution
|
|
261
|
+
- You're mid-execution on a task — Interview Mode is for session start only
|
|
262
|
+
|
|
263
|
+
**If you cannot interview, instead:**
|
|
264
|
+
1. Make a reasonable assumption based on context, conventions, and Memory
|
|
265
|
+
2. Document the assumption clearly: "Assuming X because Y — revisit if incorrect"
|
|
266
|
+
3. Proceed with execution
|
|
267
|
+
4. Note the assumption in the checkpoint/memorialization
|
|
268
|
+
|
|
269
|
+
### When to use Interview Mode (if guards pass):
|
|
270
|
+
- User's request is vague or high-level ("make it better", "add auth")
|
|
271
|
+
- Multiple valid interpretations exist
|
|
272
|
+
- Critical decisions need user input (tech stack, scope, approach)
|
|
273
|
+
- Complex feature with many unknowns
|
|
274
|
+
- **Session is just starting** (not mid-execution)
|
|
275
|
+
|
|
276
|
+
**Interview Mode workflow:**
|
|
277
|
+
1. **Acknowledge** the request and note what's unclear
|
|
278
|
+
2. **Ask targeted questions** — be specific, not open-ended
|
|
279
|
+
3. **Propose options** when applicable ("Option A: X, Option B: Y — which do you prefer?")
|
|
280
|
+
4. **Summarize understanding** before proceeding to planning
|
|
281
|
+
5. **Ask Memory** if similar work was done before
|
|
282
|
+
|
|
283
|
+
**Example:**
|
|
284
|
+
> "I want to add authentication to this app."
|
|
285
|
+
|
|
286
|
+
Interview response:
|
|
287
|
+
> Before I plan this, I need to clarify a few things:
|
|
288
|
+
> 1. **Auth provider:** Do you want to use a service (Clerk, Auth0, Supabase Auth) or build custom?
|
|
289
|
+
> 2. **Scope:** Just login/logout, or also registration, password reset, OAuth?
|
|
290
|
+
> 3. **Protected routes:** Which parts of the app need auth?
|
|
291
|
+
>
|
|
292
|
+
> Let me also ask Memory if we've done auth work in this project before.
|
|
293
|
+
|
|
294
|
+
## Ultrawork Mode (Aggressive Orchestration)
|
|
295
|
+
|
|
296
|
+
When the user signals they want autonomous, aggressive execution, enter **Ultrawork Mode**:
|
|
297
|
+
|
|
298
|
+
**Trigger keywords:** \`ultrawork\`, \`ultrathink\`, \`ulw\`, \`just do it\`, \`work hard\`, \`plan hard\`, \`take a long time\`, \`as long as you need\`, \`go deep\`, \`be thorough\`
|
|
299
|
+
|
|
300
|
+
**Ultrawork Mode behavior:**
|
|
301
|
+
1. **Micro-plan first** — Create a quick 5-10 bullet plan (don't skip planning entirely)
|
|
302
|
+
2. **Aggressive delegation** — Use FanOut pattern, run Scout in parallel for discovery
|
|
303
|
+
3. **Auto-continue** — Don't stop to ask permission; keep iterating until truly done
|
|
304
|
+
4. **Verification gates** — Still require Reviewer for non-trivial changes
|
|
305
|
+
5. **Memory checkpoints** — Store progress frequently for recovery
|
|
306
|
+
|
|
307
|
+
**Ultrawork is NOT:**
|
|
308
|
+
- Skipping quality checks
|
|
309
|
+
- Ignoring user constraints
|
|
310
|
+
- Running forever without progress signals
|
|
311
|
+
|
|
312
|
+
**When in Ultrawork Mode, default to action over asking.** If something is unclear but you can make a reasonable assumption, do so and note it. Only pause for truly blocking decisions.
|
|
313
|
+
|
|
205
314
|
## Anti-Pattern Catalog
|
|
206
315
|
|
|
207
316
|
| Anti-Pattern | Why It's Wrong | Correct Approach |
|
|
208
317
|
|--------------|----------------|------------------|
|
|
209
318
|
| Delegating planning to Scout | Scout is read-only researcher, lacks strategic view | Lead plans using ultrathink, Scout gathers info |
|
|
210
319
|
| Skipping Reviewer | Quality issues and bugs slip through | Always review non-trivial changes |
|
|
211
|
-
| Vague delegations | Subagents guess intent, fail or go off-track | Use
|
|
320
|
+
| Vague delegations | Subagents guess intent, fail or go off-track | Use 8-section delegation spec |
|
|
212
321
|
| Ignoring Memory | Context lost between sessions, repeated work | Query Memory at start, store decisions at end |
|
|
213
322
|
| Writing code directly | Lead is orchestrator, not implementer | Delegate all code work to Builder |
|
|
214
323
|
| Over-parallelizing | Dependencies cause conflicts and wasted work | Sequence dependent tasks, parallelize only independent |
|
|
@@ -534,7 +643,11 @@ Each iteration follows this pattern:
|
|
|
534
643
|
2. **Ask Memory (Corrections Gate)** — "Return ONLY corrections/gotchas relevant to this iteration (CLI flags, region config, ctx API signatures, runtime detection)." If Memory returns a correction, you MUST paste it into CONTEXT of the next delegation.
|
|
535
644
|
3. **Plan this iteration** — What's the next concrete step?
|
|
536
645
|
4. **Delegate** — Scout/Builder/Reviewer as needed
|
|
537
|
-
5. **
|
|
646
|
+
5. **Emit status tag** — Output a structured status line (plugin tracks this):
|
|
647
|
+
\`\`\`
|
|
648
|
+
CADENCE_STATUS loopId={loopId} iteration={N} maxIterations={max} status={running|paused}
|
|
649
|
+
\`\`\`
|
|
650
|
+
6. **Update KV loop state** — Increment iteration counter, update phase status:
|
|
538
651
|
\`\`\`bash
|
|
539
652
|
agentuity cloud kv set agentuity-opencode-tasks "loop:{loopId}:state" '{
|
|
540
653
|
"iteration": N+1,
|
|
@@ -543,8 +656,22 @@ Each iteration follows this pattern:
|
|
|
543
656
|
...
|
|
544
657
|
}'
|
|
545
658
|
\`\`\`
|
|
546
|
-
|
|
547
|
-
|
|
659
|
+
7. **Store checkpoint** — Tell Memory: "Store checkpoint for iteration {N}: what changed, what's next"
|
|
660
|
+
8. **Decide** — Complete? Output \`<promise>DONE</promise>\`. More work? Continue.
|
|
661
|
+
|
|
662
|
+
### Dynamic Iteration Limits
|
|
663
|
+
|
|
664
|
+
Users can adjust the iteration limit during a running loop:
|
|
665
|
+
|
|
666
|
+
| User Says | Your Action |
|
|
667
|
+
|-----------|-------------|
|
|
668
|
+
| "continue for N more iterations" | \`maxIterations = currentIteration + N\`, persist to KV |
|
|
669
|
+
| "set max iterations to N" | \`maxIterations = N\`, persist to KV |
|
|
670
|
+
| "go until done" / "as long as you need" | \`maxIterations = 200\` (high limit), persist to KV |
|
|
671
|
+
|
|
672
|
+
When maxIterations changes, immediately update KV and confirm: "Updated max iterations to {N}."
|
|
673
|
+
|
|
674
|
+
At each iteration boundary, check: if \`iteration >= maxIterations\`, pause and ask user if they want to continue.
|
|
548
675
|
|
|
549
676
|
### Completion Signal
|
|
550
677
|
|
package/src/agents/memory.ts
CHANGED
|
@@ -504,6 +504,73 @@ agentuity cloud kv set agentuity-opencode-tasks "loop:{loopId}:handoff" '{
|
|
|
504
504
|
|
|
505
505
|
A handoff packet should contain everything needed to resume work without the original conversation history.
|
|
506
506
|
|
|
507
|
+
### Compaction Memorialization
|
|
508
|
+
|
|
509
|
+
When context is about to be compacted (or has been compacted), you may be asked to capture a **rich snapshot** of the session state. This is critical for continuity in Cadence mode.
|
|
510
|
+
|
|
511
|
+
**Compaction snapshot goals:**
|
|
512
|
+
- Capture as much detail as possible so future questions can reference it
|
|
513
|
+
- Enable the session to continue seamlessly after compaction
|
|
514
|
+
- Preserve the "why" behind decisions, not just the "what"
|
|
515
|
+
|
|
516
|
+
**Compaction Snapshot Template:**
|
|
517
|
+
|
|
518
|
+
\`\`\`bash
|
|
519
|
+
agentuity cloud kv set agentuity-opencode-tasks "loop:{loopId}:compaction:{N}" '{
|
|
520
|
+
"compactionNumber": N,
|
|
521
|
+
"timestamp": "...",
|
|
522
|
+
"loopId": "lp_...",
|
|
523
|
+
"iteration": 15,
|
|
524
|
+
"currentPhase": "frontend",
|
|
525
|
+
|
|
526
|
+
"summary": "Detailed summary of what has been accomplished so far...",
|
|
527
|
+
|
|
528
|
+
"keyDecisions": [
|
|
529
|
+
{"decision": "Use Stripe Checkout", "rationale": "Simpler than custom flow, handles PCI compliance"},
|
|
530
|
+
{"decision": "JWT in httpOnly cookies", "rationale": "More secure than localStorage"}
|
|
531
|
+
],
|
|
532
|
+
|
|
533
|
+
"corrections": [
|
|
534
|
+
{"correction": "Sandbox path is /home/agentuity not /app", "context": "Commands were failing"},
|
|
535
|
+
{"correction": "Use bcrypt not md5", "context": "Security requirement"}
|
|
536
|
+
],
|
|
537
|
+
|
|
538
|
+
"codeChanges": [
|
|
539
|
+
{"file": "src/payments/stripe.ts", "change": "Created payment service with createCheckout, handleWebhook"},
|
|
540
|
+
{"file": "src/api/webhooks/stripe.ts", "change": "Added webhook endpoint with signature verification"}
|
|
541
|
+
],
|
|
542
|
+
|
|
543
|
+
"pendingWork": [
|
|
544
|
+
"Complete checkout form component",
|
|
545
|
+
"Add error handling UI",
|
|
546
|
+
"Write integration tests"
|
|
547
|
+
],
|
|
548
|
+
|
|
549
|
+
"contextNotes": [
|
|
550
|
+
"User prefers minimal dependencies",
|
|
551
|
+
"Project uses Tailwind CSS",
|
|
552
|
+
"Tests should use vitest"
|
|
553
|
+
],
|
|
554
|
+
|
|
555
|
+
"filesInScope": ["src/payments/", "src/api/webhooks/", "src/components/checkout/"],
|
|
556
|
+
|
|
557
|
+
"nextAction": "Implement CheckoutForm.tsx component with Stripe Elements"
|
|
558
|
+
}'
|
|
559
|
+
\`\`\`
|
|
560
|
+
|
|
561
|
+
**Also store a semantic summary in Vector** for future recall:
|
|
562
|
+
|
|
563
|
+
\`\`\`bash
|
|
564
|
+
agentuity cloud vector upsert agentuity-opencode-sessions "compaction:{loopId}:{N}" \\
|
|
565
|
+
--document "Compaction snapshot for loop {loopId} at iteration {iteration}. [Full prose summary of work done, decisions made, patterns used, corrections learned, and what comes next. Be comprehensive - this is the canonical record of this phase of work.]" \\
|
|
566
|
+
--metadata '{"type":"compaction","loopId":"lp_...","iteration":"15","phase":"frontend"}'
|
|
567
|
+
\`\`\`
|
|
568
|
+
|
|
569
|
+
**When answering questions about previous compaction cycles:**
|
|
570
|
+
1. Search KV for \`loop:{loopId}:compaction:*\` to find compaction snapshots
|
|
571
|
+
2. Search Vector for \`type:compaction\` to find semantic summaries
|
|
572
|
+
3. Combine findings to provide comprehensive context
|
|
573
|
+
|
|
507
574
|
### Cadence Loop Completion
|
|
508
575
|
|
|
509
576
|
When a Cadence loop completes (Lead outputs \`<promise>DONE</promise>\`):
|
|
@@ -1,24 +1,48 @@
|
|
|
1
|
-
import type { PluginContext, CoderConfig } from '../../types';
|
|
1
|
+
import type { PluginContext, CoderConfig, CompactingInput, CompactingOutput } from '../../types';
|
|
2
2
|
|
|
3
3
|
export interface CadenceHooks {
|
|
4
4
|
onMessage: (input: unknown, output: unknown) => Promise<void>;
|
|
5
5
|
onEvent: (input: unknown) => Promise<void>;
|
|
6
|
+
onCompacting: (input: CompactingInput, output: CompactingOutput) => Promise<void>;
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
const COMPLETION_PATTERN = /<promise>\s*DONE\s*<\/promise>/i;
|
|
9
10
|
|
|
11
|
+
// Ultrawork trigger keywords - case insensitive matching
|
|
12
|
+
const ULTRAWORK_TRIGGERS = [
|
|
13
|
+
'ultrawork',
|
|
14
|
+
'ultrathink',
|
|
15
|
+
'ulw',
|
|
16
|
+
'just do it',
|
|
17
|
+
'work hard',
|
|
18
|
+
'plan hard',
|
|
19
|
+
'take a long time',
|
|
20
|
+
'as long as you need',
|
|
21
|
+
'go deep',
|
|
22
|
+
'be thorough',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
// Track Cadence state per session for context injection
|
|
26
|
+
interface CadenceSessionState {
|
|
27
|
+
startedAt: string;
|
|
28
|
+
loopId?: string;
|
|
29
|
+
iteration: number;
|
|
30
|
+
maxIterations: number;
|
|
31
|
+
lastActivity: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
10
34
|
/**
|
|
11
35
|
* Cadence hooks track which sessions are in long-running Cadence mode.
|
|
12
36
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* These hooks primarily:
|
|
16
|
-
* 1. Detect when Cadence mode starts (via command or [CADENCE MODE] tag)
|
|
37
|
+
* These hooks handle:
|
|
38
|
+
* 1. Detect when Cadence mode starts (via command, [CADENCE MODE] tag, or ultrawork triggers)
|
|
17
39
|
* 2. Detect when Cadence completes (via <promise>DONE</promise>)
|
|
18
|
-
* 3.
|
|
40
|
+
* 3. Inject context during compaction (experimental.session.compacting)
|
|
41
|
+
* 4. Trigger continuation after compaction (session.compacted)
|
|
42
|
+
* 5. Clean up on session abort/error
|
|
19
43
|
*/
|
|
20
44
|
export function createCadenceHooks(ctx: PluginContext, _config: CoderConfig): CadenceHooks {
|
|
21
|
-
const activeCadenceSessions = new
|
|
45
|
+
const activeCadenceSessions = new Map<string, CadenceSessionState>();
|
|
22
46
|
|
|
23
47
|
const log = (msg: string) => {
|
|
24
48
|
ctx.client.app.log({
|
|
@@ -38,18 +62,71 @@ export function createCadenceHooks(ctx: PluginContext, _config: CoderConfig): Ca
|
|
|
38
62
|
const messageText = extractMessageText(output);
|
|
39
63
|
if (!messageText) return;
|
|
40
64
|
|
|
41
|
-
// Check if this is a Cadence start command
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
65
|
+
// Check if this is a Cadence start command or ultrawork trigger
|
|
66
|
+
const cadenceType = getCadenceTriggerType(messageText);
|
|
67
|
+
if (cadenceType && !activeCadenceSessions.has(sessionId)) {
|
|
68
|
+
log(`Cadence started for session ${sessionId} via ${cadenceType}`);
|
|
69
|
+
const now = new Date().toISOString();
|
|
70
|
+
const state: CadenceSessionState = {
|
|
71
|
+
startedAt: now,
|
|
72
|
+
iteration: 1,
|
|
73
|
+
maxIterations: 50,
|
|
74
|
+
lastActivity: now,
|
|
75
|
+
};
|
|
76
|
+
activeCadenceSessions.set(sessionId, state);
|
|
77
|
+
|
|
78
|
+
// If triggered by ultrawork keywords, inject [CADENCE MODE] tag
|
|
79
|
+
if (cadenceType === 'ultrawork') {
|
|
80
|
+
injectCadenceTag(output);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
await injectStatusMessage(ctx, sessionId, state);
|
|
45
84
|
return;
|
|
46
85
|
}
|
|
47
86
|
|
|
48
87
|
// Check if this session is in Cadence mode
|
|
49
|
-
|
|
88
|
+
const state = activeCadenceSessions.get(sessionId);
|
|
89
|
+
if (!state) {
|
|
50
90
|
return;
|
|
51
91
|
}
|
|
52
92
|
|
|
93
|
+
// Update last activity
|
|
94
|
+
state.lastActivity = new Date().toISOString();
|
|
95
|
+
|
|
96
|
+
// Try to extract structured CADENCE_STATUS tag first
|
|
97
|
+
// Format: CADENCE_STATUS loopId={id} iteration={N} maxIterations={max} status={status}
|
|
98
|
+
const statusMatch = messageText.match(
|
|
99
|
+
/CADENCE_STATUS\s+loopId=(\S+)\s+iteration=(\d+)\s+maxIterations=(\d+)\s+status=(\S+)/i
|
|
100
|
+
);
|
|
101
|
+
if (statusMatch) {
|
|
102
|
+
const [, loopId, iteration, maxIterations] = statusMatch;
|
|
103
|
+
const newIteration = parseInt(iteration, 10);
|
|
104
|
+
const newMax = parseInt(maxIterations, 10);
|
|
105
|
+
const changed =
|
|
106
|
+
state.loopId !== loopId ||
|
|
107
|
+
state.iteration !== newIteration ||
|
|
108
|
+
state.maxIterations !== newMax;
|
|
109
|
+
|
|
110
|
+
state.loopId = loopId;
|
|
111
|
+
state.iteration = newIteration;
|
|
112
|
+
state.maxIterations = newMax;
|
|
113
|
+
|
|
114
|
+
if (changed) {
|
|
115
|
+
await injectStatusMessage(ctx, sessionId, state);
|
|
116
|
+
}
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Fallback: try to extract iteration from loose "iteration: N" pattern
|
|
121
|
+
const iterMatch = messageText.match(/iteration[:\s]+(\d+)/i);
|
|
122
|
+
if (iterMatch) {
|
|
123
|
+
const newIteration = parseInt(iterMatch[1], 10);
|
|
124
|
+
if (newIteration !== state.iteration) {
|
|
125
|
+
state.iteration = newIteration;
|
|
126
|
+
await injectStatusMessage(ctx, sessionId, state);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
53
130
|
// Check for completion signal
|
|
54
131
|
if (COMPLETION_PATTERN.test(messageText)) {
|
|
55
132
|
log(`Cadence completed for session ${sessionId}`);
|
|
@@ -72,9 +149,48 @@ export function createCadenceHooks(ctx: PluginContext, _config: CoderConfig): Ca
|
|
|
72
149
|
|
|
73
150
|
log(`Event received: ${event.type}`);
|
|
74
151
|
|
|
152
|
+
// Handle session.compacted - trigger continuation after compaction completes
|
|
153
|
+
if (event.type === 'session.compacted') {
|
|
154
|
+
const sessionId = event.sessionId;
|
|
155
|
+
if (!sessionId) return;
|
|
156
|
+
|
|
157
|
+
const state = activeCadenceSessions.get(sessionId);
|
|
158
|
+
if (!state) return;
|
|
159
|
+
|
|
160
|
+
log(`Compaction completed for Cadence session ${sessionId} - triggering continuation`);
|
|
161
|
+
showToast(ctx, '🔄 Context compacted, resuming Cadence...');
|
|
162
|
+
|
|
163
|
+
// Inject continuation prompt if session.prompt is available
|
|
164
|
+
try {
|
|
165
|
+
await ctx.client.session?.prompt?.({
|
|
166
|
+
path: { id: sessionId },
|
|
167
|
+
body: {
|
|
168
|
+
parts: [
|
|
169
|
+
{
|
|
170
|
+
type: 'text',
|
|
171
|
+
text: `[CADENCE CONTINUATION]
|
|
172
|
+
|
|
173
|
+
Context was just compacted. Resume the Cadence loop:
|
|
174
|
+
|
|
175
|
+
1. Ask Memory for the latest checkpoint and any compaction snapshots
|
|
176
|
+
2. Review the current iteration state from KV
|
|
177
|
+
3. Continue with the next step in the iteration workflow
|
|
178
|
+
4. Do NOT restart from the beginning - pick up where you left off
|
|
179
|
+
|
|
180
|
+
Continue executing the task.`,
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
agent: 'Agentuity Coder Lead',
|
|
184
|
+
},
|
|
185
|
+
});
|
|
186
|
+
} catch (err) {
|
|
187
|
+
log(`Failed to inject continuation prompt: ${err}`);
|
|
188
|
+
// Continuation will rely on auto-generated "Continue if you have next steps"
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
75
192
|
// Handle session.idle - log for debugging/monitoring
|
|
76
|
-
|
|
77
|
-
if (event.type === 'session.idle') {
|
|
193
|
+
if (event.type === 'session.idle' || event.type === 'session.status') {
|
|
78
194
|
const sessionId = event.sessionId;
|
|
79
195
|
if (!sessionId) return;
|
|
80
196
|
|
|
@@ -92,6 +208,52 @@ export function createCadenceHooks(ctx: PluginContext, _config: CoderConfig): Ca
|
|
|
92
208
|
}
|
|
93
209
|
}
|
|
94
210
|
},
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Called during context compaction to inject Cadence state.
|
|
214
|
+
* This ensures the compaction summary includes critical loop state.
|
|
215
|
+
*/
|
|
216
|
+
async onCompacting(input: CompactingInput, output: CompactingOutput): Promise<void> {
|
|
217
|
+
const sessionId = input.sessionID;
|
|
218
|
+
const state = activeCadenceSessions.get(sessionId);
|
|
219
|
+
|
|
220
|
+
if (!state) {
|
|
221
|
+
// Not a Cadence session, nothing to inject
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
log(`Injecting Cadence context during compaction for session ${sessionId}`);
|
|
226
|
+
showToast(ctx, '💾 Compacting Cadence context...');
|
|
227
|
+
|
|
228
|
+
const loopIdStr = state.loopId ?? '{loopId}';
|
|
229
|
+
|
|
230
|
+
// Inject Cadence state into the compaction context
|
|
231
|
+
output.context.push(`
|
|
232
|
+
## CADENCE MODE ACTIVE
|
|
233
|
+
|
|
234
|
+
This session is running in Cadence mode (long-running autonomous loop).
|
|
235
|
+
|
|
236
|
+
**Cadence State:**
|
|
237
|
+
- Loop ID: ${state.loopId ?? 'unknown'}
|
|
238
|
+
- Started: ${state.startedAt}
|
|
239
|
+
- Iteration: ${state.iteration} / ${state.maxIterations}
|
|
240
|
+
- Last activity: ${state.lastActivity}
|
|
241
|
+
|
|
242
|
+
**CRITICAL: After compaction, you MUST:**
|
|
243
|
+
1. Ask @Agentuity Coder Memory for the latest checkpoint and compaction snapshots
|
|
244
|
+
2. Read the loop state from KV: \`agentuity cloud kv get agentuity-opencode-tasks "loop:${loopIdStr}:state"\`
|
|
245
|
+
3. Emit CADENCE_STATUS tag with current state
|
|
246
|
+
4. Continue the iteration workflow from where you left off
|
|
247
|
+
5. Do NOT restart the task from the beginning
|
|
248
|
+
|
|
249
|
+
**Memory Keys to Query:**
|
|
250
|
+
- \`loop:${loopIdStr}:state\` - Current loop state
|
|
251
|
+
- \`loop:${loopIdStr}:checkpoint:{N}\` - Iteration checkpoints
|
|
252
|
+
- \`loop:${loopIdStr}:compaction:{N}\` - Compaction snapshots
|
|
253
|
+
|
|
254
|
+
Resume the Cadence loop after this compaction completes.
|
|
255
|
+
`);
|
|
256
|
+
},
|
|
95
257
|
};
|
|
96
258
|
}
|
|
97
259
|
|
|
@@ -130,12 +292,41 @@ function extractEvent(input: unknown): { type: string; sessionId?: string } | un
|
|
|
130
292
|
const inp = input as { event?: { type?: string; properties?: Record<string, unknown> } };
|
|
131
293
|
if (!inp.event || typeof inp.event.type !== 'string') return undefined;
|
|
132
294
|
|
|
133
|
-
const sessionId =
|
|
295
|
+
const sessionId =
|
|
296
|
+
(inp.event.properties?.sessionId as string | undefined) ??
|
|
297
|
+
(inp.event.properties?.sessionID as string | undefined);
|
|
134
298
|
return { type: inp.event.type, sessionId };
|
|
135
299
|
}
|
|
136
300
|
|
|
137
|
-
|
|
138
|
-
|
|
301
|
+
type CadenceTriggerType = 'explicit' | 'ultrawork' | null;
|
|
302
|
+
|
|
303
|
+
function getCadenceTriggerType(text: string): CadenceTriggerType {
|
|
304
|
+
// Explicit cadence triggers
|
|
305
|
+
if (text.includes('[CADENCE MODE]') || text.includes('agentuity-cadence')) {
|
|
306
|
+
return 'explicit';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Check for ultrawork triggers (case insensitive)
|
|
310
|
+
const lowerText = text.toLowerCase();
|
|
311
|
+
if (ULTRAWORK_TRIGGERS.some((trigger) => lowerText.includes(trigger))) {
|
|
312
|
+
return 'ultrawork';
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
return null;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function injectCadenceTag(output: unknown): void {
|
|
319
|
+
if (typeof output !== 'object' || output === null) return;
|
|
320
|
+
|
|
321
|
+
const out = output as { parts?: Array<{ type?: string; text?: string }> };
|
|
322
|
+
if (!out.parts || !Array.isArray(out.parts)) return;
|
|
323
|
+
|
|
324
|
+
for (const part of out.parts) {
|
|
325
|
+
if (part.type === 'text' && part.text) {
|
|
326
|
+
part.text = `[CADENCE MODE]\n\n${part.text}`;
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
139
330
|
}
|
|
140
331
|
|
|
141
332
|
function isCadenceStop(text: string): boolean {
|
|
@@ -153,3 +344,40 @@ function showToast(ctx: PluginContext, message: string): void {
|
|
|
153
344
|
// Toast may not be available
|
|
154
345
|
}
|
|
155
346
|
}
|
|
347
|
+
|
|
348
|
+
async function injectStatusMessage(
|
|
349
|
+
ctx: PluginContext,
|
|
350
|
+
sessionId: string,
|
|
351
|
+
state: CadenceSessionState
|
|
352
|
+
): Promise<void> {
|
|
353
|
+
try {
|
|
354
|
+
const elapsed = getElapsedTime(state.startedAt);
|
|
355
|
+
const loopInfo = state.loopId ? ` · ${state.loopId}` : '';
|
|
356
|
+
const status = `⚡ **Cadence** · ${state.iteration}/${state.maxIterations}${loopInfo} · ${elapsed}`;
|
|
357
|
+
|
|
358
|
+
await ctx.client.session?.prompt?.({
|
|
359
|
+
path: { id: sessionId },
|
|
360
|
+
body: {
|
|
361
|
+
parts: [{ type: 'text', text: status }],
|
|
362
|
+
},
|
|
363
|
+
noReply: true,
|
|
364
|
+
});
|
|
365
|
+
} catch {
|
|
366
|
+
// Status injection may not be available
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function getElapsedTime(startedAt: string): string {
|
|
371
|
+
if (!startedAt) return '-';
|
|
372
|
+
|
|
373
|
+
const start = new Date(startedAt).getTime();
|
|
374
|
+
if (isNaN(start)) return '-';
|
|
375
|
+
|
|
376
|
+
const now = Date.now();
|
|
377
|
+
const seconds = Math.floor((now - start) / 1000);
|
|
378
|
+
|
|
379
|
+
if (seconds < 0) return '-';
|
|
380
|
+
if (seconds < 60) return `${seconds}s`;
|
|
381
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
|
|
382
|
+
return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`;
|
|
383
|
+
}
|