@opencode_weave/weave 0.7.0 → 0.7.3
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/dist/agents/agent-builder.d.ts +22 -0
- package/dist/config/schema.d.ts +5 -0
- package/dist/features/evals/baseline.d.ts +4 -0
- package/dist/features/evals/evaluators/deterministic.d.ts +2 -0
- package/dist/features/evals/evaluators/llm-judge.d.ts +2 -0
- package/dist/features/evals/executors/model-response.d.ts +2 -0
- package/dist/features/evals/executors/prompt-renderer.d.ts +2 -0
- package/dist/features/evals/index.d.ts +24 -0
- package/dist/features/evals/loader.d.ts +8 -0
- package/dist/features/evals/reporter.d.ts +2 -0
- package/dist/features/evals/runner.d.ts +7 -0
- package/dist/features/evals/schema.d.ts +478 -0
- package/dist/features/evals/storage.d.ts +7 -0
- package/dist/features/evals/targets/builtin-agent-target.d.ts +2 -0
- package/dist/features/evals/types.d.ts +223 -0
- package/dist/features/task-system/index.d.ts +6 -0
- package/dist/features/task-system/storage.d.ts +38 -0
- package/dist/features/task-system/todo-sync.d.ts +38 -0
- package/dist/features/task-system/tools/index.d.ts +3 -0
- package/dist/features/task-system/tools/task-create.d.ts +9 -0
- package/dist/features/task-system/tools/task-list.d.ts +5 -0
- package/dist/features/task-system/tools/task-update.d.ts +7 -0
- package/dist/features/task-system/types.d.ts +63 -0
- package/dist/index.js +471 -124
- package/dist/plugin/plugin-interface.d.ts +1 -0
- package/dist/shared/agent-display-names.d.ts +14 -0
- package/dist/shared/index.d.ts +1 -1
- package/package.json +5 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import { join as
|
|
2
|
+
import { join as join14 } from "path";
|
|
3
3
|
|
|
4
4
|
// src/config/loader.ts
|
|
5
5
|
import { existsSync as existsSync2, readFileSync } from "node:fs";
|
|
@@ -22,7 +22,8 @@ var AgentOverrideConfigSchema = z.object({
|
|
|
22
22
|
tools: z.record(z.string(), z.boolean()).optional(),
|
|
23
23
|
disable: z.boolean().optional(),
|
|
24
24
|
mode: z.enum(["subagent", "primary", "all"]).optional(),
|
|
25
|
-
maxTokens: z.number().optional()
|
|
25
|
+
maxTokens: z.number().optional(),
|
|
26
|
+
display_name: z.string().optional()
|
|
26
27
|
});
|
|
27
28
|
var AgentOverridesSchema = z.record(z.string(), AgentOverrideConfigSchema);
|
|
28
29
|
var CategoryConfigSchema = z.object({
|
|
@@ -52,7 +53,8 @@ var TmuxConfigSchema = z.object({
|
|
|
52
53
|
var ExperimentalConfigSchema = z.object({
|
|
53
54
|
plugin_load_timeout_ms: z.number().min(1000).optional(),
|
|
54
55
|
context_window_warning_threshold: z.number().min(0).max(1).optional(),
|
|
55
|
-
context_window_critical_threshold: z.number().min(0).max(1).optional()
|
|
56
|
+
context_window_critical_threshold: z.number().min(0).max(1).optional(),
|
|
57
|
+
task_system: z.boolean().default(true)
|
|
56
58
|
});
|
|
57
59
|
var DelegationTriggerSchema = z.object({
|
|
58
60
|
domain: z.string(),
|
|
@@ -223,6 +225,7 @@ var AGENT_DISPLAY_NAMES = {
|
|
|
223
225
|
weft: "weft"
|
|
224
226
|
};
|
|
225
227
|
var BUILTIN_CONFIG_KEYS = new Set(Object.keys(AGENT_DISPLAY_NAMES));
|
|
228
|
+
var INITIAL_BUILTIN_DISPLAY_NAMES = new Map(Object.entries(AGENT_DISPLAY_NAMES));
|
|
226
229
|
var reverseDisplayNames = null;
|
|
227
230
|
function getReverseDisplayNames() {
|
|
228
231
|
if (reverseDisplayNames === null) {
|
|
@@ -234,6 +237,11 @@ function registerAgentDisplayName(configKey, displayName) {
|
|
|
234
237
|
if (BUILTIN_CONFIG_KEYS.has(configKey)) {
|
|
235
238
|
throw new Error(`Cannot register display name for "${configKey}": it is a built-in agent name`);
|
|
236
239
|
}
|
|
240
|
+
for (const [builtinKey, initialDisplayName] of INITIAL_BUILTIN_DISPLAY_NAMES) {
|
|
241
|
+
if (initialDisplayName.toLowerCase() === displayName.toLowerCase()) {
|
|
242
|
+
throw new Error(`Display name "${displayName}" is reserved for built-in agent "${builtinKey}"`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
237
245
|
const reverse = getReverseDisplayNames();
|
|
238
246
|
const existingKey = reverse[displayName.toLowerCase()];
|
|
239
247
|
if (existingKey !== undefined && BUILTIN_CONFIG_KEYS.has(existingKey)) {
|
|
@@ -242,6 +250,13 @@ function registerAgentDisplayName(configKey, displayName) {
|
|
|
242
250
|
AGENT_DISPLAY_NAMES[configKey] = displayName;
|
|
243
251
|
reverseDisplayNames = null;
|
|
244
252
|
}
|
|
253
|
+
function updateBuiltinDisplayName(configKey, displayName) {
|
|
254
|
+
if (!BUILTIN_CONFIG_KEYS.has(configKey)) {
|
|
255
|
+
throw new Error(`Cannot update builtin display name for "${configKey}": not a built-in agent`);
|
|
256
|
+
}
|
|
257
|
+
AGENT_DISPLAY_NAMES[configKey] = displayName;
|
|
258
|
+
reverseDisplayNames = null;
|
|
259
|
+
}
|
|
245
260
|
function getAgentDisplayName(configKey) {
|
|
246
261
|
const exactMatch = AGENT_DISPLAY_NAMES[configKey];
|
|
247
262
|
if (exactMatch !== undefined)
|
|
@@ -1233,6 +1248,8 @@ Use this structure:
|
|
|
1233
1248
|
\`\`\`
|
|
1234
1249
|
|
|
1235
1250
|
CRITICAL: Use \`- [ ]\` checkboxes for ALL actionable items. The /start-work system tracks progress by counting these checkboxes.
|
|
1251
|
+
|
|
1252
|
+
Use the exact section headings shown in the template above (\`## TL;DR\`, \`## Context\`, \`## Objectives\`, \`## TODOs\`, \`## Verification\`). Consistent headings help downstream tooling parse the plan.
|
|
1236
1253
|
</PlanOutput>
|
|
1237
1254
|
|
|
1238
1255
|
<Constraints>
|
|
@@ -1725,12 +1742,19 @@ var AGENT_NAME_VARIANTS = {
|
|
|
1725
1742
|
loom: ["loom", "Loom"],
|
|
1726
1743
|
tapestry: ["tapestry", "Tapestry"]
|
|
1727
1744
|
};
|
|
1745
|
+
var INITIAL_NAME_VARIANTS = new Map(Object.entries(AGENT_NAME_VARIANTS).map(([k, v]) => [k, [...v]]));
|
|
1728
1746
|
function registerAgentNameVariants(name, variants) {
|
|
1729
1747
|
if (AGENT_NAME_VARIANTS[name])
|
|
1730
1748
|
return;
|
|
1731
1749
|
const titleCase = name.charAt(0).toUpperCase() + name.slice(1);
|
|
1732
1750
|
AGENT_NAME_VARIANTS[name] = variants ?? [name, titleCase];
|
|
1733
1751
|
}
|
|
1752
|
+
function addBuiltinNameVariant(configKey, variant) {
|
|
1753
|
+
const existing = AGENT_NAME_VARIANTS[configKey];
|
|
1754
|
+
if (existing && !existing.includes(variant)) {
|
|
1755
|
+
existing.push(variant);
|
|
1756
|
+
}
|
|
1757
|
+
}
|
|
1734
1758
|
function stripDisabledAgentReferences(prompt, disabled) {
|
|
1735
1759
|
if (disabled.size === 0)
|
|
1736
1760
|
return prompt;
|
|
@@ -1743,7 +1767,7 @@ function stripDisabledAgentReferences(prompt, disabled) {
|
|
|
1743
1767
|
}
|
|
1744
1768
|
if (disabledVariants.length === 0)
|
|
1745
1769
|
return prompt;
|
|
1746
|
-
const pattern = new RegExp(
|
|
1770
|
+
const pattern = new RegExp(`(?<!\\w)(${disabledVariants.map((v) => v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("|")})(?!\\w)`);
|
|
1747
1771
|
const lines = prompt.split(`
|
|
1748
1772
|
`);
|
|
1749
1773
|
const filtered = lines.filter((line) => !pattern.test(line));
|
|
@@ -1888,7 +1912,10 @@ var KNOWN_TOOL_NAMES = new Set([
|
|
|
1888
1912
|
"call_weave_agent",
|
|
1889
1913
|
"webfetch",
|
|
1890
1914
|
"todowrite",
|
|
1891
|
-
"skill"
|
|
1915
|
+
"skill",
|
|
1916
|
+
"task_create",
|
|
1917
|
+
"task_update",
|
|
1918
|
+
"task_list"
|
|
1892
1919
|
]);
|
|
1893
1920
|
var AGENT_NAME_PATTERN = /^[a-z][a-z0-9_-]*$/;
|
|
1894
1921
|
function parseFallbackModels(models) {
|
|
@@ -1988,6 +2015,26 @@ function createManagers(options) {
|
|
|
1988
2015
|
fingerprint,
|
|
1989
2016
|
customAgentMetadata
|
|
1990
2017
|
});
|
|
2018
|
+
if (pluginConfig.agents) {
|
|
2019
|
+
for (const [name, override] of Object.entries(pluginConfig.agents)) {
|
|
2020
|
+
const displayName = override.display_name?.trim();
|
|
2021
|
+
if (displayName) {
|
|
2022
|
+
try {
|
|
2023
|
+
updateBuiltinDisplayName(name, displayName);
|
|
2024
|
+
addBuiltinNameVariant(name, displayName);
|
|
2025
|
+
if (agents[name]) {
|
|
2026
|
+
agents[name] = { ...agents[name], description: displayName };
|
|
2027
|
+
}
|
|
2028
|
+
} catch (err) {
|
|
2029
|
+
if (err instanceof Error && err.message.includes("not a built-in agent")) {
|
|
2030
|
+
log(`Skipping display_name override for non-builtin agent "${name}"`);
|
|
2031
|
+
} else {
|
|
2032
|
+
throw err;
|
|
2033
|
+
}
|
|
2034
|
+
}
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
1991
2038
|
if (pluginConfig.custom_agents) {
|
|
1992
2039
|
const disabledSet = new Set(pluginConfig.disabled_agents ?? []);
|
|
1993
2040
|
for (const [name, customConfig] of Object.entries(pluginConfig.custom_agents)) {
|
|
@@ -2248,6 +2295,275 @@ function createSkillResolver(discovered) {
|
|
|
2248
2295
|
return resolveMultipleSkills(skillNames, disabledSkills, discovered);
|
|
2249
2296
|
};
|
|
2250
2297
|
}
|
|
2298
|
+
// src/features/task-system/tools/task-create.ts
|
|
2299
|
+
import { tool } from "@opencode-ai/plugin";
|
|
2300
|
+
|
|
2301
|
+
// src/features/task-system/storage.ts
|
|
2302
|
+
import { mkdirSync as mkdirSync2, writeFileSync, readFileSync as readFileSync4, renameSync, unlinkSync, readdirSync as readdirSync2, statSync, openSync, closeSync } from "fs";
|
|
2303
|
+
import { join as join5, basename } from "path";
|
|
2304
|
+
import { randomUUID } from "crypto";
|
|
2305
|
+
|
|
2306
|
+
// src/features/task-system/types.ts
|
|
2307
|
+
import { z as z2 } from "zod";
|
|
2308
|
+
var TaskStatus = {
|
|
2309
|
+
PENDING: "pending",
|
|
2310
|
+
IN_PROGRESS: "in_progress",
|
|
2311
|
+
COMPLETED: "completed",
|
|
2312
|
+
DELETED: "deleted"
|
|
2313
|
+
};
|
|
2314
|
+
var TaskStatusSchema = z2.enum(["pending", "in_progress", "completed", "deleted"]);
|
|
2315
|
+
var TaskObjectSchema = z2.object({
|
|
2316
|
+
id: z2.string(),
|
|
2317
|
+
subject: z2.string(),
|
|
2318
|
+
description: z2.string(),
|
|
2319
|
+
status: TaskStatusSchema,
|
|
2320
|
+
threadID: z2.string(),
|
|
2321
|
+
blocks: z2.array(z2.string()).default([]),
|
|
2322
|
+
blockedBy: z2.array(z2.string()).default([]),
|
|
2323
|
+
metadata: z2.record(z2.string(), z2.unknown()).optional()
|
|
2324
|
+
});
|
|
2325
|
+
var TaskCreateInputSchema = z2.object({
|
|
2326
|
+
subject: z2.string().describe("Short title for the task (required)"),
|
|
2327
|
+
description: z2.string().optional().describe("Detailed description of the task"),
|
|
2328
|
+
blocks: z2.array(z2.string()).optional().describe("Task IDs that this task blocks"),
|
|
2329
|
+
blockedBy: z2.array(z2.string()).optional().describe("Task IDs that block this task"),
|
|
2330
|
+
metadata: z2.record(z2.string(), z2.unknown()).optional().describe("Arbitrary key-value metadata")
|
|
2331
|
+
});
|
|
2332
|
+
var TaskUpdateInputSchema = z2.object({
|
|
2333
|
+
id: z2.string().describe("Task ID to update (required, format: T-{uuid})"),
|
|
2334
|
+
subject: z2.string().optional().describe("New subject/title"),
|
|
2335
|
+
description: z2.string().optional().describe("New description"),
|
|
2336
|
+
status: TaskStatusSchema.optional().describe("New status"),
|
|
2337
|
+
addBlocks: z2.array(z2.string()).optional().describe("Task IDs to add to blocks (additive, no replacement)"),
|
|
2338
|
+
addBlockedBy: z2.array(z2.string()).optional().describe("Task IDs to add to blockedBy (additive, no replacement)"),
|
|
2339
|
+
metadata: z2.record(z2.string(), z2.unknown()).optional().describe("Metadata to merge (null values delete keys)")
|
|
2340
|
+
});
|
|
2341
|
+
var TaskListInputSchema = z2.object({});
|
|
2342
|
+
|
|
2343
|
+
// src/features/task-system/storage.ts
|
|
2344
|
+
function getTaskDir(directory, configDir) {
|
|
2345
|
+
const base = configDir ?? join5(getHomeDir(), ".config", "opencode");
|
|
2346
|
+
const slug = sanitizeSlug(basename(directory));
|
|
2347
|
+
return join5(base, "tasks", slug);
|
|
2348
|
+
}
|
|
2349
|
+
function getHomeDir() {
|
|
2350
|
+
return process.env.HOME ?? process.env.USERPROFILE ?? "/tmp";
|
|
2351
|
+
}
|
|
2352
|
+
function sanitizeSlug(name) {
|
|
2353
|
+
return name.toLowerCase().replace(/[^a-z0-9-_]/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "") || "default";
|
|
2354
|
+
}
|
|
2355
|
+
function generateTaskId() {
|
|
2356
|
+
return `T-${randomUUID()}`;
|
|
2357
|
+
}
|
|
2358
|
+
function readJsonSafe(filePath, schema) {
|
|
2359
|
+
try {
|
|
2360
|
+
const raw = readFileSync4(filePath, "utf-8");
|
|
2361
|
+
const parsed = JSON.parse(raw);
|
|
2362
|
+
return schema.parse(parsed);
|
|
2363
|
+
} catch {
|
|
2364
|
+
return null;
|
|
2365
|
+
}
|
|
2366
|
+
}
|
|
2367
|
+
function writeJsonAtomic(filePath, data) {
|
|
2368
|
+
const dir = join5(filePath, "..");
|
|
2369
|
+
mkdirSync2(dir, { recursive: true });
|
|
2370
|
+
const tmpPath = `${filePath}.tmp`;
|
|
2371
|
+
writeFileSync(tmpPath, JSON.stringify(data, null, 2), "utf-8");
|
|
2372
|
+
renameSync(tmpPath, filePath);
|
|
2373
|
+
}
|
|
2374
|
+
function ensureDir(dirPath) {
|
|
2375
|
+
mkdirSync2(dirPath, { recursive: true });
|
|
2376
|
+
}
|
|
2377
|
+
function listTaskFiles(taskDir) {
|
|
2378
|
+
try {
|
|
2379
|
+
return readdirSync2(taskDir).filter((f) => f.startsWith("T-") && f.endsWith(".json")).map((f) => join5(taskDir, f));
|
|
2380
|
+
} catch {
|
|
2381
|
+
return [];
|
|
2382
|
+
}
|
|
2383
|
+
}
|
|
2384
|
+
function getTaskFilePath(taskDir, taskId) {
|
|
2385
|
+
return join5(taskDir, `${taskId}.json`);
|
|
2386
|
+
}
|
|
2387
|
+
function readTask(taskDir, taskId) {
|
|
2388
|
+
return readJsonSafe(getTaskFilePath(taskDir, taskId), TaskObjectSchema);
|
|
2389
|
+
}
|
|
2390
|
+
function writeTask(taskDir, task) {
|
|
2391
|
+
writeJsonAtomic(getTaskFilePath(taskDir, task.id), task);
|
|
2392
|
+
}
|
|
2393
|
+
function readAllTasks(taskDir) {
|
|
2394
|
+
const files = listTaskFiles(taskDir);
|
|
2395
|
+
const tasks = [];
|
|
2396
|
+
for (const file of files) {
|
|
2397
|
+
const task = readJsonSafe(file, TaskObjectSchema);
|
|
2398
|
+
if (task)
|
|
2399
|
+
tasks.push(task);
|
|
2400
|
+
}
|
|
2401
|
+
return tasks;
|
|
2402
|
+
}
|
|
2403
|
+
|
|
2404
|
+
// src/features/task-system/todo-sync.ts
|
|
2405
|
+
function syncTaskToTodo(task) {
|
|
2406
|
+
if (task.status === TaskStatus.DELETED) {
|
|
2407
|
+
return null;
|
|
2408
|
+
}
|
|
2409
|
+
const statusMap = {
|
|
2410
|
+
[TaskStatus.PENDING]: "pending",
|
|
2411
|
+
[TaskStatus.IN_PROGRESS]: "in_progress",
|
|
2412
|
+
[TaskStatus.COMPLETED]: "completed"
|
|
2413
|
+
};
|
|
2414
|
+
const priority = task.metadata?.priority ?? undefined;
|
|
2415
|
+
return {
|
|
2416
|
+
id: task.id,
|
|
2417
|
+
content: task.subject,
|
|
2418
|
+
status: statusMap[task.status] ?? "pending",
|
|
2419
|
+
...priority ? { priority } : {}
|
|
2420
|
+
};
|
|
2421
|
+
}
|
|
2422
|
+
function todosMatch(a, b) {
|
|
2423
|
+
if (a.id && b.id)
|
|
2424
|
+
return a.id === b.id;
|
|
2425
|
+
return a.content === b.content;
|
|
2426
|
+
}
|
|
2427
|
+
async function syncTaskTodoUpdate(writer, sessionId, task) {
|
|
2428
|
+
if (!writer) {
|
|
2429
|
+
log("[task-sync] No todo writer available — skipping sidebar sync");
|
|
2430
|
+
return;
|
|
2431
|
+
}
|
|
2432
|
+
try {
|
|
2433
|
+
const currentTodos = await writer.read(sessionId);
|
|
2434
|
+
const todoItem = syncTaskToTodo(task);
|
|
2435
|
+
const filtered = currentTodos.filter((t) => !todosMatch(t, { id: task.id, content: task.subject, status: "pending" }));
|
|
2436
|
+
if (todoItem) {
|
|
2437
|
+
filtered.push(todoItem);
|
|
2438
|
+
}
|
|
2439
|
+
await writer.update(sessionId, filtered);
|
|
2440
|
+
} catch (err) {
|
|
2441
|
+
log("[task-sync] Failed to sync task to sidebar (non-fatal)", { taskId: task.id, error: String(err) });
|
|
2442
|
+
}
|
|
2443
|
+
}
|
|
2444
|
+
|
|
2445
|
+
// src/features/task-system/tools/task-create.ts
|
|
2446
|
+
function createTaskCreateTool(options) {
|
|
2447
|
+
const { directory, configDir, todoWriter = null } = options;
|
|
2448
|
+
return tool({
|
|
2449
|
+
description: "Create a new task. Use this instead of todowrite for task tracking. " + "Each task gets a unique ID and is stored atomically — creating a task never destroys existing tasks or todos.",
|
|
2450
|
+
args: {
|
|
2451
|
+
subject: tool.schema.string().describe("Short title for the task (required)"),
|
|
2452
|
+
description: tool.schema.string().optional().describe("Detailed description of the task"),
|
|
2453
|
+
blocks: tool.schema.array(tool.schema.string()).optional().describe("Task IDs that this task blocks"),
|
|
2454
|
+
blockedBy: tool.schema.array(tool.schema.string()).optional().describe("Task IDs that block this task"),
|
|
2455
|
+
metadata: tool.schema.record(tool.schema.string(), tool.schema.unknown()).optional().describe("Arbitrary key-value metadata")
|
|
2456
|
+
},
|
|
2457
|
+
async execute(args, context) {
|
|
2458
|
+
const taskDir = getTaskDir(directory, configDir);
|
|
2459
|
+
ensureDir(taskDir);
|
|
2460
|
+
const task = {
|
|
2461
|
+
id: generateTaskId(),
|
|
2462
|
+
subject: args.subject,
|
|
2463
|
+
description: args.description ?? "",
|
|
2464
|
+
status: TaskStatus.PENDING,
|
|
2465
|
+
threadID: context.sessionID,
|
|
2466
|
+
blocks: args.blocks ?? [],
|
|
2467
|
+
blockedBy: args.blockedBy ?? [],
|
|
2468
|
+
metadata: args.metadata
|
|
2469
|
+
};
|
|
2470
|
+
writeTask(taskDir, task);
|
|
2471
|
+
log("[task-create] Created task", { id: task.id, subject: task.subject });
|
|
2472
|
+
await syncTaskTodoUpdate(todoWriter, context.sessionID, task);
|
|
2473
|
+
return JSON.stringify({ task: { id: task.id, subject: task.subject } });
|
|
2474
|
+
}
|
|
2475
|
+
});
|
|
2476
|
+
}
|
|
2477
|
+
// src/features/task-system/tools/task-update.ts
|
|
2478
|
+
import { tool as tool2 } from "@opencode-ai/plugin";
|
|
2479
|
+
var TASK_ID_PATTERN = /^T-[A-Za-z0-9-]+$/;
|
|
2480
|
+
function createTaskUpdateTool(options) {
|
|
2481
|
+
const { directory, configDir, todoWriter = null } = options;
|
|
2482
|
+
return tool2({
|
|
2483
|
+
description: "Update an existing task by ID. Modifies only the specified fields — " + "other tasks and non-task todos are completely untouched. " + "blocks/blockedBy are additive (appended, never replaced).",
|
|
2484
|
+
args: {
|
|
2485
|
+
id: tool2.schema.string().describe("Task ID to update (required, format: T-{uuid})"),
|
|
2486
|
+
subject: tool2.schema.string().optional().describe("New subject/title"),
|
|
2487
|
+
description: tool2.schema.string().optional().describe("New description"),
|
|
2488
|
+
status: tool2.schema.enum(["pending", "in_progress", "completed", "deleted"]).optional().describe("New status"),
|
|
2489
|
+
addBlocks: tool2.schema.array(tool2.schema.string()).optional().describe("Task IDs to add to blocks (additive)"),
|
|
2490
|
+
addBlockedBy: tool2.schema.array(tool2.schema.string()).optional().describe("Task IDs to add to blockedBy (additive)"),
|
|
2491
|
+
metadata: tool2.schema.record(tool2.schema.string(), tool2.schema.unknown()).optional().describe("Metadata to merge (null values delete keys)")
|
|
2492
|
+
},
|
|
2493
|
+
async execute(args, context) {
|
|
2494
|
+
if (!TASK_ID_PATTERN.test(args.id)) {
|
|
2495
|
+
return JSON.stringify({ error: "invalid_task_id", message: `Invalid task ID format: ${args.id}. Expected T-{uuid}` });
|
|
2496
|
+
}
|
|
2497
|
+
const taskDir = getTaskDir(directory, configDir);
|
|
2498
|
+
const task = readTask(taskDir, args.id);
|
|
2499
|
+
if (!task) {
|
|
2500
|
+
return JSON.stringify({ error: "task_not_found", message: `Task ${args.id} not found` });
|
|
2501
|
+
}
|
|
2502
|
+
if (args.subject !== undefined)
|
|
2503
|
+
task.subject = args.subject;
|
|
2504
|
+
if (args.description !== undefined)
|
|
2505
|
+
task.description = args.description;
|
|
2506
|
+
if (args.status !== undefined)
|
|
2507
|
+
task.status = args.status;
|
|
2508
|
+
if (args.addBlocks?.length) {
|
|
2509
|
+
const existing = new Set(task.blocks);
|
|
2510
|
+
for (const b of args.addBlocks) {
|
|
2511
|
+
if (!existing.has(b)) {
|
|
2512
|
+
task.blocks.push(b);
|
|
2513
|
+
existing.add(b);
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
if (args.addBlockedBy?.length) {
|
|
2518
|
+
const existing = new Set(task.blockedBy);
|
|
2519
|
+
for (const b of args.addBlockedBy) {
|
|
2520
|
+
if (!existing.has(b)) {
|
|
2521
|
+
task.blockedBy.push(b);
|
|
2522
|
+
existing.add(b);
|
|
2523
|
+
}
|
|
2524
|
+
}
|
|
2525
|
+
}
|
|
2526
|
+
if (args.metadata) {
|
|
2527
|
+
const meta = task.metadata ?? {};
|
|
2528
|
+
for (const [key, value] of Object.entries(args.metadata)) {
|
|
2529
|
+
if (value === null) {
|
|
2530
|
+
delete meta[key];
|
|
2531
|
+
} else {
|
|
2532
|
+
meta[key] = value;
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
task.metadata = Object.keys(meta).length > 0 ? meta : undefined;
|
|
2536
|
+
}
|
|
2537
|
+
writeTask(taskDir, task);
|
|
2538
|
+
log("[task-update] Updated task", { id: task.id });
|
|
2539
|
+
await syncTaskTodoUpdate(todoWriter, context.sessionID, task);
|
|
2540
|
+
return JSON.stringify({ task });
|
|
2541
|
+
}
|
|
2542
|
+
});
|
|
2543
|
+
}
|
|
2544
|
+
// src/features/task-system/tools/task-list.ts
|
|
2545
|
+
import { tool as tool3 } from "@opencode-ai/plugin";
|
|
2546
|
+
function createTaskListTool(options) {
|
|
2547
|
+
const { directory, configDir } = options;
|
|
2548
|
+
return tool3({
|
|
2549
|
+
description: "List all active tasks (pending and in_progress). " + "Excludes completed and deleted tasks. " + "Shows unresolved blockers for each task.",
|
|
2550
|
+
args: {},
|
|
2551
|
+
async execute(_args, _context) {
|
|
2552
|
+
const taskDir = getTaskDir(directory, configDir);
|
|
2553
|
+
const allTasks = readAllTasks(taskDir);
|
|
2554
|
+
const activeTasks = allTasks.filter((t) => t.status !== TaskStatus.COMPLETED && t.status !== TaskStatus.DELETED);
|
|
2555
|
+
const completedIds = new Set(allTasks.filter((t) => t.status === TaskStatus.COMPLETED).map((t) => t.id));
|
|
2556
|
+
const tasks = activeTasks.map((t) => ({
|
|
2557
|
+
id: t.id,
|
|
2558
|
+
subject: t.subject,
|
|
2559
|
+
status: t.status,
|
|
2560
|
+
blockedBy: t.blockedBy.filter((b) => !completedIds.has(b))
|
|
2561
|
+
}));
|
|
2562
|
+
log("[task-list] Listed tasks", { count: tasks.length });
|
|
2563
|
+
return JSON.stringify({ tasks });
|
|
2564
|
+
}
|
|
2565
|
+
});
|
|
2566
|
+
}
|
|
2251
2567
|
// src/create-tools.ts
|
|
2252
2568
|
async function createTools(options) {
|
|
2253
2569
|
const { ctx, pluginConfig } = options;
|
|
@@ -2258,6 +2574,13 @@ async function createTools(options) {
|
|
|
2258
2574
|
});
|
|
2259
2575
|
const resolveSkillsFn = createSkillResolver(skillResult);
|
|
2260
2576
|
const tools = {};
|
|
2577
|
+
if (pluginConfig.experimental?.task_system !== false) {
|
|
2578
|
+
const toolOptions = { directory: ctx.directory };
|
|
2579
|
+
tools.task_create = createTaskCreateTool(toolOptions);
|
|
2580
|
+
tools.task_update = createTaskUpdateTool(toolOptions);
|
|
2581
|
+
tools.task_list = createTaskListTool(toolOptions);
|
|
2582
|
+
log("[task-system] Registered task tools (task_create, task_update, task_list)");
|
|
2583
|
+
}
|
|
2261
2584
|
return {
|
|
2262
2585
|
tools,
|
|
2263
2586
|
availableSkills: skillResult.skills,
|
|
@@ -2450,17 +2773,17 @@ var WORK_STATE_FILE = "state.json";
|
|
|
2450
2773
|
var WORK_STATE_PATH = `${WEAVE_DIR}/${WORK_STATE_FILE}`;
|
|
2451
2774
|
var PLANS_DIR = `${WEAVE_DIR}/plans`;
|
|
2452
2775
|
// src/features/work-state/storage.ts
|
|
2453
|
-
import { existsSync as existsSync7, readFileSync as
|
|
2454
|
-
import { join as
|
|
2776
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, mkdirSync as mkdirSync3, readdirSync as readdirSync3, statSync as statSync2 } from "fs";
|
|
2777
|
+
import { join as join7, basename as basename2 } from "path";
|
|
2455
2778
|
import { execSync } from "child_process";
|
|
2456
2779
|
var UNCHECKED_RE = /^[-*]\s*\[\s*\]/gm;
|
|
2457
2780
|
var CHECKED_RE = /^[-*]\s*\[[xX]\]/gm;
|
|
2458
2781
|
function readWorkState(directory) {
|
|
2459
|
-
const filePath =
|
|
2782
|
+
const filePath = join7(directory, WEAVE_DIR, WORK_STATE_FILE);
|
|
2460
2783
|
try {
|
|
2461
2784
|
if (!existsSync7(filePath))
|
|
2462
2785
|
return null;
|
|
2463
|
-
const raw =
|
|
2786
|
+
const raw = readFileSync6(filePath, "utf-8");
|
|
2464
2787
|
const parsed = JSON.parse(raw);
|
|
2465
2788
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
2466
2789
|
return null;
|
|
@@ -2476,21 +2799,21 @@ function readWorkState(directory) {
|
|
|
2476
2799
|
}
|
|
2477
2800
|
function writeWorkState(directory, state) {
|
|
2478
2801
|
try {
|
|
2479
|
-
const dir =
|
|
2802
|
+
const dir = join7(directory, WEAVE_DIR);
|
|
2480
2803
|
if (!existsSync7(dir)) {
|
|
2481
|
-
|
|
2804
|
+
mkdirSync3(dir, { recursive: true });
|
|
2482
2805
|
}
|
|
2483
|
-
|
|
2806
|
+
writeFileSync2(join7(dir, WORK_STATE_FILE), JSON.stringify(state, null, 2), "utf-8");
|
|
2484
2807
|
return true;
|
|
2485
2808
|
} catch {
|
|
2486
2809
|
return false;
|
|
2487
2810
|
}
|
|
2488
2811
|
}
|
|
2489
2812
|
function clearWorkState(directory) {
|
|
2490
|
-
const filePath =
|
|
2813
|
+
const filePath = join7(directory, WEAVE_DIR, WORK_STATE_FILE);
|
|
2491
2814
|
try {
|
|
2492
2815
|
if (existsSync7(filePath)) {
|
|
2493
|
-
|
|
2816
|
+
unlinkSync2(filePath);
|
|
2494
2817
|
}
|
|
2495
2818
|
return true;
|
|
2496
2819
|
} catch {
|
|
@@ -2531,13 +2854,13 @@ function getHeadSha(directory) {
|
|
|
2531
2854
|
}
|
|
2532
2855
|
}
|
|
2533
2856
|
function findPlans(directory) {
|
|
2534
|
-
const plansDir =
|
|
2857
|
+
const plansDir = join7(directory, PLANS_DIR);
|
|
2535
2858
|
try {
|
|
2536
2859
|
if (!existsSync7(plansDir))
|
|
2537
2860
|
return [];
|
|
2538
|
-
const files =
|
|
2539
|
-
const fullPath =
|
|
2540
|
-
const stat =
|
|
2861
|
+
const files = readdirSync3(plansDir).filter((f) => f.endsWith(".md")).map((f) => {
|
|
2862
|
+
const fullPath = join7(plansDir, f);
|
|
2863
|
+
const stat = statSync2(fullPath);
|
|
2541
2864
|
return { path: fullPath, mtime: stat.mtimeMs };
|
|
2542
2865
|
}).sort((a, b) => b.mtime - a.mtime).map((f) => f.path);
|
|
2543
2866
|
return files;
|
|
@@ -2550,7 +2873,7 @@ function getPlanProgress(planPath) {
|
|
|
2550
2873
|
return { total: 0, completed: 0, isComplete: true };
|
|
2551
2874
|
}
|
|
2552
2875
|
try {
|
|
2553
|
-
const content =
|
|
2876
|
+
const content = readFileSync6(planPath, "utf-8");
|
|
2554
2877
|
const unchecked = content.match(UNCHECKED_RE) || [];
|
|
2555
2878
|
const checked = content.match(CHECKED_RE) || [];
|
|
2556
2879
|
const total = unchecked.length + checked.length;
|
|
@@ -2565,7 +2888,7 @@ function getPlanProgress(planPath) {
|
|
|
2565
2888
|
}
|
|
2566
2889
|
}
|
|
2567
2890
|
function getPlanName(planPath) {
|
|
2568
|
-
return
|
|
2891
|
+
return basename2(planPath, ".md");
|
|
2569
2892
|
}
|
|
2570
2893
|
function pauseWork(directory) {
|
|
2571
2894
|
const state = readWorkState(directory);
|
|
@@ -2582,7 +2905,7 @@ function resumeWork(directory) {
|
|
|
2582
2905
|
return writeWorkState(directory, state);
|
|
2583
2906
|
}
|
|
2584
2907
|
// src/features/work-state/validation.ts
|
|
2585
|
-
import { readFileSync as
|
|
2908
|
+
import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
|
|
2586
2909
|
import { resolve as resolve3, sep as sep2 } from "path";
|
|
2587
2910
|
function validatePlan(planPath, projectDir) {
|
|
2588
2911
|
const errors = [];
|
|
@@ -2605,13 +2928,13 @@ function validatePlan(planPath, projectDir) {
|
|
|
2605
2928
|
});
|
|
2606
2929
|
return { valid: false, errors, warnings };
|
|
2607
2930
|
}
|
|
2608
|
-
const content =
|
|
2931
|
+
const content = readFileSync7(resolvedPlanPath, "utf-8");
|
|
2609
2932
|
validateStructure(content, errors, warnings);
|
|
2610
2933
|
validateCheckboxes(content, errors, warnings);
|
|
2611
2934
|
validateFileReferences(content, projectDir, warnings);
|
|
2612
2935
|
validateNumbering(content, errors, warnings);
|
|
2613
2936
|
validateEffortEstimate(content, warnings);
|
|
2614
|
-
validateVerificationSection(content,
|
|
2937
|
+
validateVerificationSection(content, warnings);
|
|
2615
2938
|
return {
|
|
2616
2939
|
valid: errors.length === 0,
|
|
2617
2940
|
errors,
|
|
@@ -2643,15 +2966,15 @@ function hasSection(content, heading) {
|
|
|
2643
2966
|
return content.split(`
|
|
2644
2967
|
`).some((line) => line.trim() === heading);
|
|
2645
2968
|
}
|
|
2646
|
-
function validateStructure(content,
|
|
2647
|
-
const
|
|
2648
|
-
["## TL;DR", "Missing
|
|
2649
|
-
["## TODOs", "Missing
|
|
2650
|
-
["## Verification", "Missing
|
|
2969
|
+
function validateStructure(content, _errors, warnings) {
|
|
2970
|
+
const expectedSections = [
|
|
2971
|
+
["## TL;DR", "Missing expected section: ## TL;DR"],
|
|
2972
|
+
["## TODOs", "Missing expected section: ## TODOs"],
|
|
2973
|
+
["## Verification", "Missing expected section: ## Verification"]
|
|
2651
2974
|
];
|
|
2652
|
-
for (const [heading, message] of
|
|
2975
|
+
for (const [heading, message] of expectedSections) {
|
|
2653
2976
|
if (!hasSection(content, heading)) {
|
|
2654
|
-
|
|
2977
|
+
warnings.push({ severity: "warning", category: "structure", message });
|
|
2655
2978
|
}
|
|
2656
2979
|
}
|
|
2657
2980
|
const optionalSections = [
|
|
@@ -2667,6 +2990,14 @@ function validateStructure(content, errors, warnings) {
|
|
|
2667
2990
|
function validateCheckboxes(content, errors, warnings) {
|
|
2668
2991
|
const todosSection = extractSection(content, "## TODOs");
|
|
2669
2992
|
if (todosSection === null) {
|
|
2993
|
+
const hasAnyCheckbox = /^- \[[ x]\] /m.test(content);
|
|
2994
|
+
if (!hasAnyCheckbox) {
|
|
2995
|
+
errors.push({
|
|
2996
|
+
severity: "error",
|
|
2997
|
+
category: "checkboxes",
|
|
2998
|
+
message: "Plan contains no checkboxes (- [ ] or - [x]) — nothing to execute"
|
|
2999
|
+
});
|
|
3000
|
+
}
|
|
2670
3001
|
return;
|
|
2671
3002
|
}
|
|
2672
3003
|
const checkboxPattern = /^- \[[ x]\] /m;
|
|
@@ -2847,17 +3178,17 @@ function validateEffortEstimate(content, warnings) {
|
|
|
2847
3178
|
});
|
|
2848
3179
|
}
|
|
2849
3180
|
}
|
|
2850
|
-
function validateVerificationSection(content,
|
|
3181
|
+
function validateVerificationSection(content, warnings) {
|
|
2851
3182
|
const verificationSection = extractSection(content, "## Verification");
|
|
2852
3183
|
if (verificationSection === null) {
|
|
2853
3184
|
return;
|
|
2854
3185
|
}
|
|
2855
3186
|
const hasCheckbox = /^- \[[ x]\] /m.test(verificationSection);
|
|
2856
3187
|
if (!hasCheckbox) {
|
|
2857
|
-
|
|
2858
|
-
severity: "
|
|
3188
|
+
warnings.push({
|
|
3189
|
+
severity: "warning",
|
|
2859
3190
|
category: "verification",
|
|
2860
|
-
message: "## Verification section contains no checkboxes —
|
|
3191
|
+
message: "## Verification section contains no checkboxes — consider adding verifiable conditions"
|
|
2861
3192
|
});
|
|
2862
3193
|
}
|
|
2863
3194
|
}
|
|
@@ -2868,8 +3199,8 @@ var ACTIVE_INSTANCE_FILE = "active-instance.json";
|
|
|
2868
3199
|
var WORKFLOWS_DIR_PROJECT = ".opencode/workflows";
|
|
2869
3200
|
var WORKFLOWS_DIR_USER = "workflows";
|
|
2870
3201
|
// src/features/workflow/storage.ts
|
|
2871
|
-
import { existsSync as existsSync9, readFileSync as
|
|
2872
|
-
import { join as
|
|
3202
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync3, unlinkSync as unlinkSync3, mkdirSync as mkdirSync4, readdirSync as readdirSync4 } from "fs";
|
|
3203
|
+
import { join as join8 } from "path";
|
|
2873
3204
|
import { randomBytes } from "node:crypto";
|
|
2874
3205
|
function generateInstanceId() {
|
|
2875
3206
|
return `wf_${randomBytes(4).toString("hex")}`;
|
|
@@ -2905,11 +3236,11 @@ function createWorkflowInstance(definition, definitionPath, goal, sessionId) {
|
|
|
2905
3236
|
};
|
|
2906
3237
|
}
|
|
2907
3238
|
function readWorkflowInstance(directory, instanceId) {
|
|
2908
|
-
const filePath =
|
|
3239
|
+
const filePath = join8(directory, WORKFLOWS_STATE_DIR, instanceId, INSTANCE_STATE_FILE);
|
|
2909
3240
|
try {
|
|
2910
3241
|
if (!existsSync9(filePath))
|
|
2911
3242
|
return null;
|
|
2912
|
-
const raw =
|
|
3243
|
+
const raw = readFileSync8(filePath, "utf-8");
|
|
2913
3244
|
const parsed = JSON.parse(raw);
|
|
2914
3245
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
2915
3246
|
return null;
|
|
@@ -2922,22 +3253,22 @@ function readWorkflowInstance(directory, instanceId) {
|
|
|
2922
3253
|
}
|
|
2923
3254
|
function writeWorkflowInstance(directory, instance) {
|
|
2924
3255
|
try {
|
|
2925
|
-
const dir =
|
|
3256
|
+
const dir = join8(directory, WORKFLOWS_STATE_DIR, instance.instance_id);
|
|
2926
3257
|
if (!existsSync9(dir)) {
|
|
2927
|
-
|
|
3258
|
+
mkdirSync4(dir, { recursive: true });
|
|
2928
3259
|
}
|
|
2929
|
-
|
|
3260
|
+
writeFileSync3(join8(dir, INSTANCE_STATE_FILE), JSON.stringify(instance, null, 2), "utf-8");
|
|
2930
3261
|
return true;
|
|
2931
3262
|
} catch {
|
|
2932
3263
|
return false;
|
|
2933
3264
|
}
|
|
2934
3265
|
}
|
|
2935
3266
|
function readActiveInstance(directory) {
|
|
2936
|
-
const filePath =
|
|
3267
|
+
const filePath = join8(directory, WORKFLOWS_STATE_DIR, ACTIVE_INSTANCE_FILE);
|
|
2937
3268
|
try {
|
|
2938
3269
|
if (!existsSync9(filePath))
|
|
2939
3270
|
return null;
|
|
2940
|
-
const raw =
|
|
3271
|
+
const raw = readFileSync8(filePath, "utf-8");
|
|
2941
3272
|
const parsed = JSON.parse(raw);
|
|
2942
3273
|
if (!parsed || typeof parsed !== "object" || typeof parsed.instance_id !== "string")
|
|
2943
3274
|
return null;
|
|
@@ -2948,22 +3279,22 @@ function readActiveInstance(directory) {
|
|
|
2948
3279
|
}
|
|
2949
3280
|
function setActiveInstance(directory, instanceId) {
|
|
2950
3281
|
try {
|
|
2951
|
-
const dir =
|
|
3282
|
+
const dir = join8(directory, WORKFLOWS_STATE_DIR);
|
|
2952
3283
|
if (!existsSync9(dir)) {
|
|
2953
|
-
|
|
3284
|
+
mkdirSync4(dir, { recursive: true });
|
|
2954
3285
|
}
|
|
2955
3286
|
const pointer = { instance_id: instanceId };
|
|
2956
|
-
|
|
3287
|
+
writeFileSync3(join8(dir, ACTIVE_INSTANCE_FILE), JSON.stringify(pointer, null, 2), "utf-8");
|
|
2957
3288
|
return true;
|
|
2958
3289
|
} catch {
|
|
2959
3290
|
return false;
|
|
2960
3291
|
}
|
|
2961
3292
|
}
|
|
2962
3293
|
function clearActiveInstance(directory) {
|
|
2963
|
-
const filePath =
|
|
3294
|
+
const filePath = join8(directory, WORKFLOWS_STATE_DIR, ACTIVE_INSTANCE_FILE);
|
|
2964
3295
|
try {
|
|
2965
3296
|
if (existsSync9(filePath)) {
|
|
2966
|
-
|
|
3297
|
+
unlinkSync3(filePath);
|
|
2967
3298
|
}
|
|
2968
3299
|
return true;
|
|
2969
3300
|
} catch {
|
|
@@ -2983,35 +3314,35 @@ import * as os3 from "os";
|
|
|
2983
3314
|
import { parse as parseJsonc } from "jsonc-parser";
|
|
2984
3315
|
|
|
2985
3316
|
// src/features/workflow/schema.ts
|
|
2986
|
-
import { z as
|
|
2987
|
-
var CompletionConfigSchema =
|
|
2988
|
-
method:
|
|
2989
|
-
plan_name:
|
|
2990
|
-
keywords:
|
|
3317
|
+
import { z as z3 } from "zod";
|
|
3318
|
+
var CompletionConfigSchema = z3.object({
|
|
3319
|
+
method: z3.enum(["user_confirm", "plan_created", "plan_complete", "review_verdict", "agent_signal"]),
|
|
3320
|
+
plan_name: z3.string().optional(),
|
|
3321
|
+
keywords: z3.array(z3.string()).optional()
|
|
2991
3322
|
});
|
|
2992
|
-
var ArtifactRefSchema =
|
|
2993
|
-
name:
|
|
2994
|
-
description:
|
|
3323
|
+
var ArtifactRefSchema = z3.object({
|
|
3324
|
+
name: z3.string(),
|
|
3325
|
+
description: z3.string().optional()
|
|
2995
3326
|
});
|
|
2996
|
-
var StepArtifactsSchema =
|
|
2997
|
-
inputs:
|
|
2998
|
-
outputs:
|
|
3327
|
+
var StepArtifactsSchema = z3.object({
|
|
3328
|
+
inputs: z3.array(ArtifactRefSchema).optional(),
|
|
3329
|
+
outputs: z3.array(ArtifactRefSchema).optional()
|
|
2999
3330
|
});
|
|
3000
|
-
var WorkflowStepSchema =
|
|
3001
|
-
id:
|
|
3002
|
-
name:
|
|
3003
|
-
type:
|
|
3004
|
-
agent:
|
|
3005
|
-
prompt:
|
|
3331
|
+
var WorkflowStepSchema = z3.object({
|
|
3332
|
+
id: z3.string().regex(/^[a-z][a-z0-9-]*$/, "Step ID must be lowercase alphanumeric with hyphens"),
|
|
3333
|
+
name: z3.string(),
|
|
3334
|
+
type: z3.enum(["interactive", "autonomous", "gate"]),
|
|
3335
|
+
agent: z3.string(),
|
|
3336
|
+
prompt: z3.string(),
|
|
3006
3337
|
completion: CompletionConfigSchema,
|
|
3007
3338
|
artifacts: StepArtifactsSchema.optional(),
|
|
3008
|
-
on_reject:
|
|
3339
|
+
on_reject: z3.enum(["pause", "fail"]).optional()
|
|
3009
3340
|
});
|
|
3010
|
-
var WorkflowDefinitionSchema =
|
|
3011
|
-
name:
|
|
3012
|
-
description:
|
|
3013
|
-
version:
|
|
3014
|
-
steps:
|
|
3341
|
+
var WorkflowDefinitionSchema = z3.object({
|
|
3342
|
+
name: z3.string().regex(/^[a-z][a-z0-9-]*$/, "Workflow name must be lowercase alphanumeric with hyphens"),
|
|
3343
|
+
description: z3.string().optional(),
|
|
3344
|
+
version: z3.number().int().positive(),
|
|
3345
|
+
steps: z3.array(WorkflowStepSchema).min(1, "Workflow must have at least one step")
|
|
3015
3346
|
});
|
|
3016
3347
|
|
|
3017
3348
|
// src/features/workflow/discovery.ts
|
|
@@ -3153,7 +3484,7 @@ function truncateSummary(text) {
|
|
|
3153
3484
|
}
|
|
3154
3485
|
// src/features/workflow/completion.ts
|
|
3155
3486
|
import { existsSync as existsSync11 } from "fs";
|
|
3156
|
-
import { join as
|
|
3487
|
+
import { join as join10 } from "path";
|
|
3157
3488
|
var DEFAULT_CONFIRM_KEYWORDS = ["confirmed", "approved", "continue", "done", "let's proceed", "looks good", "lgtm"];
|
|
3158
3489
|
var VERDICT_APPROVE_RE = /\[\s*APPROVE\s*\]/i;
|
|
3159
3490
|
var VERDICT_REJECT_RE = /\[\s*REJECT\s*\]/i;
|
|
@@ -3205,7 +3536,7 @@ function checkPlanCreated(context) {
|
|
|
3205
3536
|
summary: `Plan created at ${matchingPlan}`
|
|
3206
3537
|
};
|
|
3207
3538
|
}
|
|
3208
|
-
const directPath =
|
|
3539
|
+
const directPath = join10(directory, ".weave", "plans", `${planName}.md`);
|
|
3209
3540
|
if (existsSync11(directPath)) {
|
|
3210
3541
|
return {
|
|
3211
3542
|
complete: true,
|
|
@@ -3221,7 +3552,7 @@ function checkPlanComplete(context) {
|
|
|
3221
3552
|
if (!planName) {
|
|
3222
3553
|
return { complete: false, reason: "plan_complete requires plan_name in completion config" };
|
|
3223
3554
|
}
|
|
3224
|
-
const planPath =
|
|
3555
|
+
const planPath = join10(directory, ".weave", "plans", `${planName}.md`);
|
|
3225
3556
|
if (!existsSync11(planPath)) {
|
|
3226
3557
|
return { complete: false, reason: `Plan file not found: ${planPath}` };
|
|
3227
3558
|
}
|
|
@@ -4183,8 +4514,8 @@ function clearSession2(sessionId) {
|
|
|
4183
4514
|
sessionMap.delete(sessionId);
|
|
4184
4515
|
}
|
|
4185
4516
|
// src/features/analytics/storage.ts
|
|
4186
|
-
import { existsSync as existsSync12, mkdirSync as
|
|
4187
|
-
import { join as
|
|
4517
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync5, appendFileSync as appendFileSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync4, statSync as statSync3 } from "fs";
|
|
4518
|
+
import { join as join11 } from "path";
|
|
4188
4519
|
|
|
4189
4520
|
// src/features/analytics/types.ts
|
|
4190
4521
|
var ANALYTICS_DIR = ".weave/analytics";
|
|
@@ -4199,30 +4530,30 @@ function zeroTokenUsage() {
|
|
|
4199
4530
|
// src/features/analytics/storage.ts
|
|
4200
4531
|
var MAX_SESSION_ENTRIES = 1000;
|
|
4201
4532
|
function ensureAnalyticsDir(directory) {
|
|
4202
|
-
const dir =
|
|
4203
|
-
|
|
4533
|
+
const dir = join11(directory, ANALYTICS_DIR);
|
|
4534
|
+
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
4204
4535
|
return dir;
|
|
4205
4536
|
}
|
|
4206
4537
|
function appendSessionSummary(directory, summary) {
|
|
4207
4538
|
try {
|
|
4208
4539
|
const dir = ensureAnalyticsDir(directory);
|
|
4209
|
-
const filePath =
|
|
4540
|
+
const filePath = join11(dir, SESSION_SUMMARIES_FILE);
|
|
4210
4541
|
const line = JSON.stringify(summary) + `
|
|
4211
4542
|
`;
|
|
4212
4543
|
appendFileSync2(filePath, line, { encoding: "utf-8", mode: 384 });
|
|
4213
4544
|
try {
|
|
4214
4545
|
const TYPICAL_ENTRY_BYTES = 200;
|
|
4215
4546
|
const rotationSizeThreshold = MAX_SESSION_ENTRIES * TYPICAL_ENTRY_BYTES * 0.9;
|
|
4216
|
-
const { size } =
|
|
4547
|
+
const { size } = statSync3(filePath);
|
|
4217
4548
|
if (size > rotationSizeThreshold) {
|
|
4218
|
-
const content =
|
|
4549
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
4219
4550
|
const lines = content.split(`
|
|
4220
4551
|
`).filter((l) => l.trim().length > 0);
|
|
4221
4552
|
if (lines.length > MAX_SESSION_ENTRIES) {
|
|
4222
4553
|
const trimmed = lines.slice(-MAX_SESSION_ENTRIES).join(`
|
|
4223
4554
|
`) + `
|
|
4224
4555
|
`;
|
|
4225
|
-
|
|
4556
|
+
writeFileSync4(filePath, trimmed, { encoding: "utf-8", mode: 384 });
|
|
4226
4557
|
}
|
|
4227
4558
|
}
|
|
4228
4559
|
} catch {}
|
|
@@ -4232,11 +4563,11 @@ function appendSessionSummary(directory, summary) {
|
|
|
4232
4563
|
}
|
|
4233
4564
|
}
|
|
4234
4565
|
function readSessionSummaries(directory) {
|
|
4235
|
-
const filePath =
|
|
4566
|
+
const filePath = join11(directory, ANALYTICS_DIR, SESSION_SUMMARIES_FILE);
|
|
4236
4567
|
try {
|
|
4237
4568
|
if (!existsSync12(filePath))
|
|
4238
4569
|
return [];
|
|
4239
|
-
const content =
|
|
4570
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
4240
4571
|
const lines = content.split(`
|
|
4241
4572
|
`).filter((line) => line.trim().length > 0);
|
|
4242
4573
|
const summaries = [];
|
|
@@ -4253,19 +4584,19 @@ function readSessionSummaries(directory) {
|
|
|
4253
4584
|
function writeFingerprint(directory, fingerprint) {
|
|
4254
4585
|
try {
|
|
4255
4586
|
const dir = ensureAnalyticsDir(directory);
|
|
4256
|
-
const filePath =
|
|
4257
|
-
|
|
4587
|
+
const filePath = join11(dir, FINGERPRINT_FILE);
|
|
4588
|
+
writeFileSync4(filePath, JSON.stringify(fingerprint, null, 2), { encoding: "utf-8", mode: 384 });
|
|
4258
4589
|
return true;
|
|
4259
4590
|
} catch {
|
|
4260
4591
|
return false;
|
|
4261
4592
|
}
|
|
4262
4593
|
}
|
|
4263
4594
|
function readFingerprint(directory) {
|
|
4264
|
-
const filePath =
|
|
4595
|
+
const filePath = join11(directory, ANALYTICS_DIR, FINGERPRINT_FILE);
|
|
4265
4596
|
try {
|
|
4266
4597
|
if (!existsSync12(filePath))
|
|
4267
4598
|
return null;
|
|
4268
|
-
const content =
|
|
4599
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
4269
4600
|
const parsed = JSON.parse(content);
|
|
4270
4601
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.stack))
|
|
4271
4602
|
return null;
|
|
@@ -4277,23 +4608,23 @@ function readFingerprint(directory) {
|
|
|
4277
4608
|
function writeMetricsReport(directory, report) {
|
|
4278
4609
|
try {
|
|
4279
4610
|
const dir = ensureAnalyticsDir(directory);
|
|
4280
|
-
const filePath =
|
|
4611
|
+
const filePath = join11(dir, METRICS_REPORTS_FILE);
|
|
4281
4612
|
const line = JSON.stringify(report) + `
|
|
4282
4613
|
`;
|
|
4283
4614
|
appendFileSync2(filePath, line, { encoding: "utf-8", mode: 384 });
|
|
4284
4615
|
try {
|
|
4285
4616
|
const TYPICAL_ENTRY_BYTES = 200;
|
|
4286
4617
|
const rotationSizeThreshold = MAX_METRICS_ENTRIES * TYPICAL_ENTRY_BYTES * 0.9;
|
|
4287
|
-
const { size } =
|
|
4618
|
+
const { size } = statSync3(filePath);
|
|
4288
4619
|
if (size > rotationSizeThreshold) {
|
|
4289
|
-
const content =
|
|
4620
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
4290
4621
|
const lines = content.split(`
|
|
4291
4622
|
`).filter((l) => l.trim().length > 0);
|
|
4292
4623
|
if (lines.length > MAX_METRICS_ENTRIES) {
|
|
4293
4624
|
const trimmed = lines.slice(-MAX_METRICS_ENTRIES).join(`
|
|
4294
4625
|
`) + `
|
|
4295
4626
|
`;
|
|
4296
|
-
|
|
4627
|
+
writeFileSync4(filePath, trimmed, { encoding: "utf-8", mode: 384 });
|
|
4297
4628
|
}
|
|
4298
4629
|
}
|
|
4299
4630
|
} catch {}
|
|
@@ -4303,11 +4634,11 @@ function writeMetricsReport(directory, report) {
|
|
|
4303
4634
|
}
|
|
4304
4635
|
}
|
|
4305
4636
|
function readMetricsReports(directory) {
|
|
4306
|
-
const filePath =
|
|
4637
|
+
const filePath = join11(directory, ANALYTICS_DIR, METRICS_REPORTS_FILE);
|
|
4307
4638
|
try {
|
|
4308
4639
|
if (!existsSync12(filePath))
|
|
4309
4640
|
return [];
|
|
4310
|
-
const content =
|
|
4641
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
4311
4642
|
const lines = content.split(`
|
|
4312
4643
|
`).filter((line) => line.trim().length > 0);
|
|
4313
4644
|
const reports = [];
|
|
@@ -4468,7 +4799,7 @@ function topTools(summaries, limit = 5) {
|
|
|
4468
4799
|
counts[t.tool] = (counts[t.tool] ?? 0) + t.count;
|
|
4469
4800
|
}
|
|
4470
4801
|
}
|
|
4471
|
-
return Object.entries(counts).map(([
|
|
4802
|
+
return Object.entries(counts).map(([tool4, count]) => ({ tool: tool4, count })).sort((a, b) => b.count - a.count).slice(0, limit);
|
|
4472
4803
|
}
|
|
4473
4804
|
function formatMetricsMarkdown(reports, summaries, args) {
|
|
4474
4805
|
if (reports.length === 0 && summaries.length === 0) {
|
|
@@ -4533,7 +4864,7 @@ function formatMetricsMarkdown(reports, summaries, args) {
|
|
|
4533
4864
|
}
|
|
4534
4865
|
|
|
4535
4866
|
// src/features/analytics/plan-parser.ts
|
|
4536
|
-
import { readFileSync as
|
|
4867
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
4537
4868
|
function extractSection2(content, heading) {
|
|
4538
4869
|
const lines = content.split(`
|
|
4539
4870
|
`);
|
|
@@ -4568,7 +4899,7 @@ function extractFilePath2(raw) {
|
|
|
4568
4899
|
function extractPlannedFiles(planPath) {
|
|
4569
4900
|
let content;
|
|
4570
4901
|
try {
|
|
4571
|
-
content =
|
|
4902
|
+
content = readFileSync11(planPath, "utf-8");
|
|
4572
4903
|
} catch {
|
|
4573
4904
|
return [];
|
|
4574
4905
|
}
|
|
@@ -4718,7 +5049,7 @@ function generateMetricsReport(directory, state) {
|
|
|
4718
5049
|
// src/plugin/plugin-interface.ts
|
|
4719
5050
|
var FINALIZE_TODOS_MARKER = "<!-- weave:finalize-todos -->";
|
|
4720
5051
|
function createPluginInterface(args) {
|
|
4721
|
-
const { pluginConfig, hooks, tools, configHandler, agents, client, directory = "", tracker } = args;
|
|
5052
|
+
const { pluginConfig, hooks, tools, configHandler, agents, client, directory = "", tracker, taskSystemEnabled = false } = args;
|
|
4722
5053
|
const lastAssistantMessageText = new Map;
|
|
4723
5054
|
const lastUserMessageText = new Map;
|
|
4724
5055
|
const todoFinalizedSessions = new Set;
|
|
@@ -4730,9 +5061,24 @@ function createPluginInterface(args) {
|
|
|
4730
5061
|
agents,
|
|
4731
5062
|
availableTools: []
|
|
4732
5063
|
});
|
|
4733
|
-
config.agent
|
|
4734
|
-
|
|
4735
|
-
|
|
5064
|
+
const existingAgents = config.agent ?? {};
|
|
5065
|
+
if (Object.keys(existingAgents).length > 0) {
|
|
5066
|
+
log("[config] Merging Weave agents over existing agents", {
|
|
5067
|
+
existingCount: Object.keys(existingAgents).length,
|
|
5068
|
+
weaveCount: Object.keys(result.agents).length,
|
|
5069
|
+
existingKeys: Object.keys(existingAgents)
|
|
5070
|
+
});
|
|
5071
|
+
const collisions = Object.keys(result.agents).filter((key) => (key in existingAgents));
|
|
5072
|
+
if (collisions.length > 0) {
|
|
5073
|
+
log("[config] Weave agents overriding user-defined agents with same name", {
|
|
5074
|
+
overriddenKeys: collisions
|
|
5075
|
+
});
|
|
5076
|
+
}
|
|
5077
|
+
}
|
|
5078
|
+
config.agent = { ...existingAgents, ...result.agents };
|
|
5079
|
+
const existingCommands = config.command ?? {};
|
|
5080
|
+
config.command = { ...existingCommands, ...result.commands };
|
|
5081
|
+
if (result.defaultAgent && !config.default_agent) {
|
|
4736
5082
|
config.default_agent = result.defaultAgent;
|
|
4737
5083
|
}
|
|
4738
5084
|
},
|
|
@@ -4804,7 +5150,7 @@ ${result.contextInjection}`;
|
|
|
4804
5150
|
`).trim() ?? "";
|
|
4805
5151
|
if (userText && sessionID) {
|
|
4806
5152
|
lastUserMessageText.set(sessionID, userText);
|
|
4807
|
-
if (!userText.includes(FINALIZE_TODOS_MARKER)) {
|
|
5153
|
+
if (!taskSystemEnabled && !userText.includes(FINALIZE_TODOS_MARKER)) {
|
|
4808
5154
|
todoFinalizedSessions.delete(sessionID);
|
|
4809
5155
|
}
|
|
4810
5156
|
}
|
|
@@ -4841,7 +5187,7 @@ ${cmdResult.contextInjection}`;
|
|
|
4841
5187
|
const isStartWork = promptText.includes("<session-context>");
|
|
4842
5188
|
const isContinuation = promptText.includes(CONTINUATION_MARKER);
|
|
4843
5189
|
const isWorkflowContinuation = promptText.includes(WORKFLOW_CONTINUATION_MARKER);
|
|
4844
|
-
const isTodoFinalize = promptText.includes(FINALIZE_TODOS_MARKER);
|
|
5190
|
+
const isTodoFinalize = !taskSystemEnabled && promptText.includes(FINALIZE_TODOS_MARKER);
|
|
4845
5191
|
const isActiveWorkflow = (() => {
|
|
4846
5192
|
const wf = getActiveWorkflowInstance(directory);
|
|
4847
5193
|
return wf != null && wf.status === "running";
|
|
@@ -5025,7 +5371,7 @@ ${cmdResult.contextInjection}`;
|
|
|
5025
5371
|
}
|
|
5026
5372
|
}
|
|
5027
5373
|
}
|
|
5028
|
-
if (event.type === "session.idle" && client && !continuationFired) {
|
|
5374
|
+
if (event.type === "session.idle" && client && !continuationFired && !taskSystemEnabled) {
|
|
5029
5375
|
const evt = event;
|
|
5030
5376
|
const sessionId = evt.properties?.sessionID ?? "";
|
|
5031
5377
|
if (sessionId && !todoFinalizedSessions.has(sessionId)) {
|
|
@@ -5141,14 +5487,14 @@ Use todowrite NOW to mark all of them as "completed" (or "cancelled" if abandone
|
|
|
5141
5487
|
};
|
|
5142
5488
|
}
|
|
5143
5489
|
// src/features/analytics/fingerprint.ts
|
|
5144
|
-
import { existsSync as existsSync13, readFileSync as
|
|
5145
|
-
import { join as
|
|
5490
|
+
import { existsSync as existsSync13, readFileSync as readFileSync13, readdirSync as readdirSync6 } from "fs";
|
|
5491
|
+
import { join as join13 } from "path";
|
|
5146
5492
|
import { arch } from "os";
|
|
5147
5493
|
|
|
5148
5494
|
// src/shared/version.ts
|
|
5149
|
-
import { readFileSync as
|
|
5495
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
5150
5496
|
import { fileURLToPath } from "url";
|
|
5151
|
-
import { dirname as dirname2, join as
|
|
5497
|
+
import { dirname as dirname2, join as join12 } from "path";
|
|
5152
5498
|
var cachedVersion;
|
|
5153
5499
|
function getWeaveVersion() {
|
|
5154
5500
|
if (cachedVersion !== undefined)
|
|
@@ -5157,7 +5503,7 @@ function getWeaveVersion() {
|
|
|
5157
5503
|
const thisDir = dirname2(fileURLToPath(import.meta.url));
|
|
5158
5504
|
for (const rel of ["../../package.json", "../package.json"]) {
|
|
5159
5505
|
try {
|
|
5160
|
-
const pkg = JSON.parse(
|
|
5506
|
+
const pkg = JSON.parse(readFileSync12(join12(thisDir, rel), "utf-8"));
|
|
5161
5507
|
if (pkg.name === "@opencode_weave/weave" && typeof pkg.version === "string") {
|
|
5162
5508
|
const version = pkg.version;
|
|
5163
5509
|
cachedVersion = version;
|
|
@@ -5262,7 +5608,7 @@ function detectStack(directory) {
|
|
|
5262
5608
|
const detected = [];
|
|
5263
5609
|
for (const marker of STACK_MARKERS) {
|
|
5264
5610
|
for (const file of marker.files) {
|
|
5265
|
-
if (existsSync13(
|
|
5611
|
+
if (existsSync13(join13(directory, file))) {
|
|
5266
5612
|
detected.push({
|
|
5267
5613
|
name: marker.name,
|
|
5268
5614
|
confidence: marker.confidence,
|
|
@@ -5273,9 +5619,9 @@ function detectStack(directory) {
|
|
|
5273
5619
|
}
|
|
5274
5620
|
}
|
|
5275
5621
|
try {
|
|
5276
|
-
const pkgPath =
|
|
5622
|
+
const pkgPath = join13(directory, "package.json");
|
|
5277
5623
|
if (existsSync13(pkgPath)) {
|
|
5278
|
-
const pkg = JSON.parse(
|
|
5624
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
5279
5625
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
5280
5626
|
if (deps.react) {
|
|
5281
5627
|
detected.push({
|
|
@@ -5288,7 +5634,7 @@ function detectStack(directory) {
|
|
|
5288
5634
|
} catch {}
|
|
5289
5635
|
if (!detected.some((d) => d.name === "dotnet")) {
|
|
5290
5636
|
try {
|
|
5291
|
-
const entries =
|
|
5637
|
+
const entries = readdirSync6(directory);
|
|
5292
5638
|
const dotnetFile = entries.find((e) => e.endsWith(".csproj") || e.endsWith(".fsproj") || e.endsWith(".sln"));
|
|
5293
5639
|
if (dotnetFile) {
|
|
5294
5640
|
detected.push({
|
|
@@ -5308,27 +5654,27 @@ function detectStack(directory) {
|
|
|
5308
5654
|
});
|
|
5309
5655
|
}
|
|
5310
5656
|
function detectPackageManager(directory) {
|
|
5311
|
-
if (existsSync13(
|
|
5657
|
+
if (existsSync13(join13(directory, "bun.lockb")))
|
|
5312
5658
|
return "bun";
|
|
5313
|
-
if (existsSync13(
|
|
5659
|
+
if (existsSync13(join13(directory, "pnpm-lock.yaml")))
|
|
5314
5660
|
return "pnpm";
|
|
5315
|
-
if (existsSync13(
|
|
5661
|
+
if (existsSync13(join13(directory, "yarn.lock")))
|
|
5316
5662
|
return "yarn";
|
|
5317
|
-
if (existsSync13(
|
|
5663
|
+
if (existsSync13(join13(directory, "package-lock.json")))
|
|
5318
5664
|
return "npm";
|
|
5319
|
-
if (existsSync13(
|
|
5665
|
+
if (existsSync13(join13(directory, "package.json")))
|
|
5320
5666
|
return "npm";
|
|
5321
5667
|
return;
|
|
5322
5668
|
}
|
|
5323
5669
|
function detectMonorepo(directory) {
|
|
5324
5670
|
for (const marker of MONOREPO_MARKERS) {
|
|
5325
|
-
if (existsSync13(
|
|
5671
|
+
if (existsSync13(join13(directory, marker)))
|
|
5326
5672
|
return true;
|
|
5327
5673
|
}
|
|
5328
5674
|
try {
|
|
5329
|
-
const pkgPath =
|
|
5675
|
+
const pkgPath = join13(directory, "package.json");
|
|
5330
5676
|
if (existsSync13(pkgPath)) {
|
|
5331
|
-
const pkg = JSON.parse(
|
|
5677
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
5332
5678
|
if (pkg.workspaces)
|
|
5333
5679
|
return true;
|
|
5334
5680
|
}
|
|
@@ -5486,7 +5832,7 @@ class SessionTracker {
|
|
|
5486
5832
|
const now = new Date;
|
|
5487
5833
|
const startedAt = new Date(session.startedAt);
|
|
5488
5834
|
const durationMs = now.getTime() - startedAt.getTime();
|
|
5489
|
-
const toolUsage = Object.entries(session.toolCounts).map(([
|
|
5835
|
+
const toolUsage = Object.entries(session.toolCounts).map(([tool4, count]) => ({ tool: tool4, count }));
|
|
5490
5836
|
const totalToolCalls = toolUsage.reduce((sum, entry) => sum + entry.count, 0);
|
|
5491
5837
|
const summary = {
|
|
5492
5838
|
sessionId,
|
|
@@ -5548,7 +5894,7 @@ var WeavePlugin = async (ctx) => {
|
|
|
5548
5894
|
const analyticsEnabled = pluginConfig.analytics?.enabled === true;
|
|
5549
5895
|
const fingerprintEnabled = analyticsEnabled && pluginConfig.analytics?.use_fingerprint === true;
|
|
5550
5896
|
const fingerprint = fingerprintEnabled ? getOrCreateFingerprint(ctx.directory) : null;
|
|
5551
|
-
const configDir =
|
|
5897
|
+
const configDir = join14(ctx.directory, ".opencode");
|
|
5552
5898
|
const toolsResult = await createTools({ ctx, pluginConfig });
|
|
5553
5899
|
const managers = createManagers({ ctx, pluginConfig, resolveSkills: toolsResult.resolveSkillsFn, fingerprint, configDir });
|
|
5554
5900
|
const hooks = createHooks({ pluginConfig, isHookEnabled, directory: ctx.directory, analyticsEnabled });
|
|
@@ -5561,7 +5907,8 @@ var WeavePlugin = async (ctx) => {
|
|
|
5561
5907
|
agents: managers.agents,
|
|
5562
5908
|
client: ctx.client,
|
|
5563
5909
|
directory: ctx.directory,
|
|
5564
|
-
tracker: analytics?.tracker
|
|
5910
|
+
tracker: analytics?.tracker,
|
|
5911
|
+
taskSystemEnabled: pluginConfig.experimental?.task_system !== false
|
|
5565
5912
|
});
|
|
5566
5913
|
};
|
|
5567
5914
|
var src_default = WeavePlugin;
|