@calltelemetry/openclaw-linear 0.9.16 → 0.9.17
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/package.json +1 -1
- package/src/infra/tmux-runner.ts +157 -531
- package/src/infra/tmux.ts +18 -132
- package/src/tools/planner-tools.ts +1 -0
- package/src/tools/steering-tools.ts +56 -91
- package/src/infra/token-refresh-timer.ts +0 -44
- package/src/pipeline/memory-search.ts +0 -40
- package/src/pipeline/retro.ts +0 -231
package/src/pipeline/retro.ts
DELETED
|
@@ -1,231 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* retro.ts — Post-dispatch retrospective spawner.
|
|
3
|
-
*
|
|
4
|
-
* After a dispatch completes (pass, fail, or stuck), spawns a sub-agent
|
|
5
|
-
* that analyzes the full interaction and writes a structured retrospective
|
|
6
|
-
* to the shared coding directory. Past retros are discoverable via QMD
|
|
7
|
-
* memory_search, enabling pattern detection across dispatches.
|
|
8
|
-
*
|
|
9
|
-
* Output format: YAML frontmatter + markdown sections with priority tags
|
|
10
|
-
* and actionable recommendations.
|
|
11
|
-
*/
|
|
12
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
13
|
-
import { join } from "node:path";
|
|
14
|
-
import { homedir } from "node:os";
|
|
15
|
-
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
16
|
-
import { runAgent } from "../agent/agent.js";
|
|
17
|
-
import { readWorkerOutputs, readAuditVerdicts, readLog } from "./artifacts.js";
|
|
18
|
-
import type { ActiveDispatch, CompletedDispatch } from "./dispatch-state.js";
|
|
19
|
-
import type { AuditVerdict } from "./pipeline.js";
|
|
20
|
-
import type { HookContext } from "./pipeline.js";
|
|
21
|
-
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
// Shared coding directory resolution
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Resolve the shared coding directory for retrospective files.
|
|
28
|
-
*
|
|
29
|
-
* Resolution order:
|
|
30
|
-
* 1. pluginConfig.retroDir (explicit override, supports ~/ expansion)
|
|
31
|
-
* 2. api.runtime.config.loadConfig().stateDir + "/shared/coding"
|
|
32
|
-
* 3. Fallback: ~/.openclaw/shared/coding
|
|
33
|
-
*/
|
|
34
|
-
export function resolveSharedCodingDir(
|
|
35
|
-
api: OpenClawPluginApi,
|
|
36
|
-
pluginConfig?: Record<string, unknown>,
|
|
37
|
-
): string {
|
|
38
|
-
// 1. Explicit plugin config override (supports ~/expansion)
|
|
39
|
-
const custom = pluginConfig?.retroDir as string | undefined;
|
|
40
|
-
if (custom) {
|
|
41
|
-
return custom.startsWith("~/") ? custom.replace("~", homedir()) : custom;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// 2. Derive from gateway config stateDir
|
|
45
|
-
try {
|
|
46
|
-
const config = (api as any).runtime.config.loadConfig() as Record<string, any>;
|
|
47
|
-
const rawStateDir = config?.stateDir as string | undefined;
|
|
48
|
-
const stateDir = rawStateDir
|
|
49
|
-
? (rawStateDir.startsWith("~/")
|
|
50
|
-
? rawStateDir.replace("~", homedir())
|
|
51
|
-
: rawStateDir)
|
|
52
|
-
: join(homedir(), ".openclaw");
|
|
53
|
-
return join(stateDir, "shared", "coding");
|
|
54
|
-
} catch {
|
|
55
|
-
// 3. Safe fallback
|
|
56
|
-
return join(homedir(), ".openclaw", "shared", "coding");
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
// Agent ID resolution (mirrors webhook.ts pattern)
|
|
62
|
-
// ---------------------------------------------------------------------------
|
|
63
|
-
|
|
64
|
-
function resolveAgentId(api: OpenClawPluginApi): string {
|
|
65
|
-
const pluginConfig = (api as any).pluginConfig as Record<string, unknown> | undefined;
|
|
66
|
-
const fromConfig = pluginConfig?.defaultAgentId;
|
|
67
|
-
if (typeof fromConfig === "string" && fromConfig) return fromConfig;
|
|
68
|
-
return "default";
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// ---------------------------------------------------------------------------
|
|
72
|
-
// Retrospective artifacts interface
|
|
73
|
-
// ---------------------------------------------------------------------------
|
|
74
|
-
|
|
75
|
-
export interface RetroArtifacts {
|
|
76
|
-
verdict?: AuditVerdict;
|
|
77
|
-
summary?: string;
|
|
78
|
-
prUrl?: string;
|
|
79
|
-
workerOutputs: string[];
|
|
80
|
-
auditVerdicts: string[];
|
|
81
|
-
logEntries: string[];
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ---------------------------------------------------------------------------
|
|
85
|
-
// spawnRetrospective
|
|
86
|
-
// ---------------------------------------------------------------------------
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Spawn a sub-agent to create a structured retrospective after dispatch
|
|
90
|
-
* completion. The agent analyzes worker outputs, audit verdicts, and the
|
|
91
|
-
* interaction log, then writes a retro file with YAML frontmatter.
|
|
92
|
-
*
|
|
93
|
-
* Designed to be called fire-and-forget (non-blocking):
|
|
94
|
-
* void spawnRetrospective(hookCtx, dispatch, artifacts).catch(...)
|
|
95
|
-
*
|
|
96
|
-
* The retro agent has access to memory_search to find past retros and
|
|
97
|
-
* detect recurring patterns.
|
|
98
|
-
*/
|
|
99
|
-
export async function spawnRetrospective(
|
|
100
|
-
hookCtx: HookContext,
|
|
101
|
-
dispatch: ActiveDispatch | CompletedDispatch,
|
|
102
|
-
artifacts: RetroArtifacts,
|
|
103
|
-
): Promise<void> {
|
|
104
|
-
const { api, pluginConfig } = hookCtx;
|
|
105
|
-
const TAG = `[retro:${dispatch.issueIdentifier}]`;
|
|
106
|
-
|
|
107
|
-
const codingDir = resolveSharedCodingDir(api, pluginConfig);
|
|
108
|
-
|
|
109
|
-
// Ensure the shared coding directory exists
|
|
110
|
-
if (!existsSync(codingDir)) {
|
|
111
|
-
mkdirSync(codingDir, { recursive: true });
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Compute duration from dispatchedAt if available (ActiveDispatch has it)
|
|
115
|
-
let durationMs: number | undefined;
|
|
116
|
-
if ("dispatchedAt" in dispatch && dispatch.dispatchedAt) {
|
|
117
|
-
durationMs = Date.now() - new Date(dispatch.dispatchedAt).getTime();
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Resolve fields that may differ between ActiveDispatch and CompletedDispatch
|
|
121
|
-
const issueTitle = ("issueTitle" in dispatch ? dispatch.issueTitle : undefined) ?? dispatch.issueIdentifier;
|
|
122
|
-
const model = "model" in dispatch ? dispatch.model : "unknown";
|
|
123
|
-
const tier = dispatch.tier;
|
|
124
|
-
const status = dispatch.status;
|
|
125
|
-
const attempt = "attempt" in dispatch
|
|
126
|
-
? (dispatch.attempt ?? 0)
|
|
127
|
-
: ("totalAttempts" in dispatch ? (dispatch.totalAttempts ?? 0) : 0);
|
|
128
|
-
const worktreePath = "worktreePath" in dispatch ? dispatch.worktreePath : undefined;
|
|
129
|
-
|
|
130
|
-
const dateStr = new Date().toISOString().slice(0, 10);
|
|
131
|
-
const retroFilename = `retro-${dateStr}-${dispatch.issueIdentifier}.md`;
|
|
132
|
-
const retroPath = join(codingDir, retroFilename);
|
|
133
|
-
|
|
134
|
-
// Build the retro prompt
|
|
135
|
-
const retroPrompt = [
|
|
136
|
-
`You are a coding retrospective analyst. A dispatch just completed.`,
|
|
137
|
-
`Analyze the interaction and create a structured retrospective.`,
|
|
138
|
-
``,
|
|
139
|
-
`## Dispatch Details`,
|
|
140
|
-
`- Issue: ${dispatch.issueIdentifier} — ${issueTitle}`,
|
|
141
|
-
`- Backend: ${model} | Tier: ${tier}`,
|
|
142
|
-
`- Attempts: ${attempt + 1} | Status: ${status}`,
|
|
143
|
-
worktreePath ? `- Worktree: ${worktreePath}` : "",
|
|
144
|
-
durationMs != null ? `- Duration: ${Math.round(durationMs / 1000)}s` : "",
|
|
145
|
-
artifacts.prUrl ? `- PR: ${artifacts.prUrl}` : "",
|
|
146
|
-
``,
|
|
147
|
-
`## Worker Outputs (${artifacts.workerOutputs.length} attempts)`,
|
|
148
|
-
artifacts.workerOutputs
|
|
149
|
-
.map((o, i) => `### Attempt ${i}\n${o.slice(0, 3000)}`)
|
|
150
|
-
.join("\n\n"),
|
|
151
|
-
``,
|
|
152
|
-
`## Audit Verdicts`,
|
|
153
|
-
artifacts.auditVerdicts
|
|
154
|
-
.map((v, i) => `### Attempt ${i}\n${v}`)
|
|
155
|
-
.join("\n\n"),
|
|
156
|
-
``,
|
|
157
|
-
`## Interaction Log`,
|
|
158
|
-
artifacts.logEntries.length > 0
|
|
159
|
-
? artifacts.logEntries.slice(-20).map((e) => `- ${e}`).join("\n")
|
|
160
|
-
: "(no log entries)",
|
|
161
|
-
``,
|
|
162
|
-
`## Instructions`,
|
|
163
|
-
``,
|
|
164
|
-
`**First**, use \`memory_search\` to find past retros related to this issue's`,
|
|
165
|
-
`domain, backend, or error patterns. Look for recurring friction points.`,
|
|
166
|
-
``,
|
|
167
|
-
`**Then**, write a retrospective file to: ${retroPath}`,
|
|
168
|
-
``,
|
|
169
|
-
`Use this exact format — YAML frontmatter followed by markdown sections:`,
|
|
170
|
-
``,
|
|
171
|
-
"```yaml",
|
|
172
|
-
`---`,
|
|
173
|
-
`type: retro`,
|
|
174
|
-
`issue: ${dispatch.issueIdentifier}`,
|
|
175
|
-
`title: "${issueTitle}"`,
|
|
176
|
-
`backend: ${model}`,
|
|
177
|
-
`tier: ${tier}`,
|
|
178
|
-
`status: ${status}`,
|
|
179
|
-
`attempts: ${attempt + 1}`,
|
|
180
|
-
`date: ${dateStr}`,
|
|
181
|
-
durationMs != null ? `duration_ms: ${durationMs}` : "",
|
|
182
|
-
`---`,
|
|
183
|
-
"```",
|
|
184
|
-
``,
|
|
185
|
-
`Sections (all required):`,
|
|
186
|
-
``,
|
|
187
|
-
`## Summary`,
|
|
188
|
-
`Brief description of what was done and outcome.`,
|
|
189
|
-
``,
|
|
190
|
-
`## What Went Well`,
|
|
191
|
-
`- [P1/P2/P3] Items that worked. Tag each with priority.`,
|
|
192
|
-
``,
|
|
193
|
-
`## Friction Points`,
|
|
194
|
-
`- [P1/P2/P3:CATEGORY] Issues encountered.`,
|
|
195
|
-
` Categories: PROCESS, ENV, TOOLING, PROMPT, CONFIG`,
|
|
196
|
-
``,
|
|
197
|
-
`## Environment Issues`,
|
|
198
|
-
`- [P1/P2/P3:ENV] Environment-specific problems.`,
|
|
199
|
-
``,
|
|
200
|
-
`## Recommendations`,
|
|
201
|
-
`- [RECOMMEND:target] Specific changes to prevent future friction.`,
|
|
202
|
-
` Targets: AGENTS.md, CLAUDE.md, config, prompt, codex-config, etc.`,
|
|
203
|
-
``,
|
|
204
|
-
`## Actionable Items`,
|
|
205
|
-
`- [ ] Checkbox items for follow-up.`,
|
|
206
|
-
``,
|
|
207
|
-
`Focus on patterns that would help FUTURE dispatches succeed faster:`,
|
|
208
|
-
`- What context was missing that caused extra attempts?`,
|
|
209
|
-
`- What environment setup tripped up the agent?`,
|
|
210
|
-
`- What prompt improvements would have helped?`,
|
|
211
|
-
`- What bootstrap file changes would prevent this friction?`,
|
|
212
|
-
`- Compare against past retros — are we seeing recurring patterns?`,
|
|
213
|
-
].filter(Boolean).join("\n");
|
|
214
|
-
|
|
215
|
-
api.logger.info(`${TAG} spawning retrospective agent → ${retroPath}`);
|
|
216
|
-
|
|
217
|
-
const agentId = resolveAgentId(api);
|
|
218
|
-
|
|
219
|
-
try {
|
|
220
|
-
await runAgent({
|
|
221
|
-
api,
|
|
222
|
-
agentId,
|
|
223
|
-
sessionId: `retro-${dispatch.issueIdentifier}-${Date.now()}`,
|
|
224
|
-
message: retroPrompt,
|
|
225
|
-
timeoutMs: 120_000, // 2min max for retro
|
|
226
|
-
});
|
|
227
|
-
api.logger.info(`${TAG} retrospective complete → ${retroFilename}`);
|
|
228
|
-
} catch (err) {
|
|
229
|
-
api.logger.warn(`${TAG} retrospective agent failed: ${err}`);
|
|
230
|
-
}
|
|
231
|
-
}
|