@opencode_weave/weave 0.7.1 → 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/config/schema.d.ts +2 -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 +428 -122
- package/dist/plugin/plugin-interface.d.ts +1 -0
- package/package.json +1 -1
package/dist/config/schema.d.ts
CHANGED
|
@@ -84,6 +84,7 @@ export declare const ExperimentalConfigSchema: z.ZodObject<{
|
|
|
84
84
|
plugin_load_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
85
85
|
context_window_warning_threshold: z.ZodOptional<z.ZodNumber>;
|
|
86
86
|
context_window_critical_threshold: z.ZodOptional<z.ZodNumber>;
|
|
87
|
+
task_system: z.ZodDefault<z.ZodBoolean>;
|
|
87
88
|
}, z.core.$strip>;
|
|
88
89
|
export declare const DelegationTriggerSchema: z.ZodObject<{
|
|
89
90
|
domain: z.ZodString;
|
|
@@ -258,6 +259,7 @@ export declare const WeaveConfigSchema: z.ZodObject<{
|
|
|
258
259
|
plugin_load_timeout_ms: z.ZodOptional<z.ZodNumber>;
|
|
259
260
|
context_window_warning_threshold: z.ZodOptional<z.ZodNumber>;
|
|
260
261
|
context_window_critical_threshold: z.ZodOptional<z.ZodNumber>;
|
|
262
|
+
task_system: z.ZodDefault<z.ZodBoolean>;
|
|
261
263
|
}, z.core.$strip>>;
|
|
262
264
|
workflows: z.ZodOptional<z.ZodObject<{
|
|
263
265
|
disabled_workflows: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { createTaskCreateTool, createTaskUpdateTool, createTaskListTool } from "./tools";
|
|
2
|
+
export { TaskStatus, TaskObjectSchema, TaskStatusSchema } from "./types";
|
|
3
|
+
export type { TaskObject, TaskCreateInput, TaskUpdateInput, TaskListInput } from "./types";
|
|
4
|
+
export { getTaskDir, generateTaskId, readTask, writeTask, readAllTasks } from "./storage";
|
|
5
|
+
export { syncTaskToTodo, syncTaskTodoUpdate, syncAllTasksToTodos } from "./todo-sync";
|
|
6
|
+
export type { TodoWriter, TodoInfo } from "./todo-sync";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type TaskObject } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Derive the task storage directory for a given project.
|
|
4
|
+
* Uses the opencode config dir (~/.config/opencode by default) + sanitized project slug.
|
|
5
|
+
*/
|
|
6
|
+
export declare function getTaskDir(directory: string, configDir?: string): string;
|
|
7
|
+
/** Generate a unique task ID */
|
|
8
|
+
export declare function generateTaskId(): string;
|
|
9
|
+
/**
|
|
10
|
+
* Read and parse a JSON file safely. Returns null for missing, corrupt, or invalid data.
|
|
11
|
+
*/
|
|
12
|
+
export declare function readJsonSafe<T>(filePath: string, schema: {
|
|
13
|
+
parse: (data: unknown) => T;
|
|
14
|
+
}): T | null;
|
|
15
|
+
/**
|
|
16
|
+
* Write JSON atomically: write to a temp file then rename.
|
|
17
|
+
* This prevents partial writes from corrupting the target file.
|
|
18
|
+
*/
|
|
19
|
+
export declare function writeJsonAtomic(filePath: string, data: unknown): void;
|
|
20
|
+
/**
|
|
21
|
+
* Acquire a file-based lock. Uses exclusive file creation (wx flag).
|
|
22
|
+
* Returns a release function on success, null on failure.
|
|
23
|
+
*
|
|
24
|
+
* Stale locks older than `staleThresholdMs` (default 30s) are automatically broken.
|
|
25
|
+
*/
|
|
26
|
+
export declare function acquireLock(lockPath: string, staleThresholdMs?: number): (() => void) | null;
|
|
27
|
+
/** Ensure a directory exists */
|
|
28
|
+
export declare function ensureDir(dirPath: string): void;
|
|
29
|
+
/** List task files (T-*.json) in the task directory */
|
|
30
|
+
export declare function listTaskFiles(taskDir: string): string[];
|
|
31
|
+
/** Get the file path for a task by ID */
|
|
32
|
+
export declare function getTaskFilePath(taskDir: string, taskId: string): string;
|
|
33
|
+
/** Read a single task from file storage */
|
|
34
|
+
export declare function readTask(taskDir: string, taskId: string): TaskObject | null;
|
|
35
|
+
/** Write a single task to file storage (atomic) */
|
|
36
|
+
export declare function writeTask(taskDir: string, task: TaskObject): void;
|
|
37
|
+
/** Read all tasks from a task directory */
|
|
38
|
+
export declare function readAllTasks(taskDir: string): TaskObject[];
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { TaskObject } from "./types";
|
|
2
|
+
/** TodoInfo matches the shape expected by OpenCode's todo sidebar */
|
|
3
|
+
export interface TodoInfo {
|
|
4
|
+
id?: string;
|
|
5
|
+
content: string;
|
|
6
|
+
status: "pending" | "in_progress" | "completed";
|
|
7
|
+
priority?: "high" | "medium" | "low";
|
|
8
|
+
}
|
|
9
|
+
/** TodoWriter interface — abstracts the OpenCode todo write API */
|
|
10
|
+
export interface TodoWriter {
|
|
11
|
+
read(sessionId: string): Promise<TodoInfo[]>;
|
|
12
|
+
update(sessionId: string, todos: TodoInfo[]): Promise<void>;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Map a TaskObject to a TodoInfo for the sidebar.
|
|
16
|
+
* Returns null for deleted tasks (they should be removed from the sidebar).
|
|
17
|
+
*/
|
|
18
|
+
export declare function syncTaskToTodo(task: TaskObject): TodoInfo | null;
|
|
19
|
+
/**
|
|
20
|
+
* Check if two todo items match by ID first, then by content as fallback.
|
|
21
|
+
*/
|
|
22
|
+
export declare function todosMatch(a: TodoInfo, b: TodoInfo): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Sync a single task to the todo sidebar.
|
|
25
|
+
* This is the anti-obliteration mechanism:
|
|
26
|
+
* 1. Read current todos
|
|
27
|
+
* 2. Filter out the matching item (by ID or content)
|
|
28
|
+
* 3. Push the updated item (or omit it if deleted)
|
|
29
|
+
* 4. Write back the full list
|
|
30
|
+
*
|
|
31
|
+
* Non-task todos (those not matching any task ID) survive intact.
|
|
32
|
+
*/
|
|
33
|
+
export declare function syncTaskTodoUpdate(writer: TodoWriter | null, sessionId: string, task: TaskObject): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Sync all tasks to the todo sidebar, preserving non-task todos.
|
|
36
|
+
* Used for bulk reconciliation.
|
|
37
|
+
*/
|
|
38
|
+
export declare function syncAllTasksToTodos(writer: TodoWriter | null, sessionId: string, tasks: TaskObject[]): Promise<void>;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ToolDefinition } from "@opencode-ai/plugin";
|
|
2
|
+
import { type TodoWriter } from "../todo-sync";
|
|
3
|
+
declare const TASK_ID_PATTERN: RegExp;
|
|
4
|
+
export declare function createTaskCreateTool(options: {
|
|
5
|
+
directory: string;
|
|
6
|
+
configDir?: string;
|
|
7
|
+
todoWriter?: TodoWriter | null;
|
|
8
|
+
}): ToolDefinition;
|
|
9
|
+
export { TASK_ID_PATTERN };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
/** Task status values */
|
|
3
|
+
export declare const TaskStatus: {
|
|
4
|
+
readonly PENDING: "pending";
|
|
5
|
+
readonly IN_PROGRESS: "in_progress";
|
|
6
|
+
readonly COMPLETED: "completed";
|
|
7
|
+
readonly DELETED: "deleted";
|
|
8
|
+
};
|
|
9
|
+
export type TaskStatus = (typeof TaskStatus)[keyof typeof TaskStatus];
|
|
10
|
+
export declare const TaskStatusSchema: z.ZodEnum<{
|
|
11
|
+
pending: "pending";
|
|
12
|
+
completed: "completed";
|
|
13
|
+
in_progress: "in_progress";
|
|
14
|
+
deleted: "deleted";
|
|
15
|
+
}>;
|
|
16
|
+
/**
|
|
17
|
+
* Core task object — simplified from OmO's schema.
|
|
18
|
+
* Drops: activeForm, owner, repoURL, parentID (per design decision D4).
|
|
19
|
+
*/
|
|
20
|
+
export declare const TaskObjectSchema: z.ZodObject<{
|
|
21
|
+
id: z.ZodString;
|
|
22
|
+
subject: z.ZodString;
|
|
23
|
+
description: z.ZodString;
|
|
24
|
+
status: z.ZodEnum<{
|
|
25
|
+
pending: "pending";
|
|
26
|
+
completed: "completed";
|
|
27
|
+
in_progress: "in_progress";
|
|
28
|
+
deleted: "deleted";
|
|
29
|
+
}>;
|
|
30
|
+
threadID: z.ZodString;
|
|
31
|
+
blocks: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
32
|
+
blockedBy: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
33
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
34
|
+
}, z.core.$strip>;
|
|
35
|
+
export type TaskObject = z.infer<typeof TaskObjectSchema>;
|
|
36
|
+
/** Input schema for task_create tool */
|
|
37
|
+
export declare const TaskCreateInputSchema: z.ZodObject<{
|
|
38
|
+
subject: z.ZodString;
|
|
39
|
+
description: z.ZodOptional<z.ZodString>;
|
|
40
|
+
blocks: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
41
|
+
blockedBy: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
42
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
43
|
+
}, z.core.$strip>;
|
|
44
|
+
export type TaskCreateInput = z.infer<typeof TaskCreateInputSchema>;
|
|
45
|
+
/** Input schema for task_update tool */
|
|
46
|
+
export declare const TaskUpdateInputSchema: z.ZodObject<{
|
|
47
|
+
id: z.ZodString;
|
|
48
|
+
subject: z.ZodOptional<z.ZodString>;
|
|
49
|
+
description: z.ZodOptional<z.ZodString>;
|
|
50
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
51
|
+
pending: "pending";
|
|
52
|
+
completed: "completed";
|
|
53
|
+
in_progress: "in_progress";
|
|
54
|
+
deleted: "deleted";
|
|
55
|
+
}>>;
|
|
56
|
+
addBlocks: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
57
|
+
addBlockedBy: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
58
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
59
|
+
}, z.core.$strip>;
|
|
60
|
+
export type TaskUpdateInput = z.infer<typeof TaskUpdateInputSchema>;
|
|
61
|
+
/** Input schema for task_list tool (no args needed for PoC) */
|
|
62
|
+
export declare const TaskListInputSchema: z.ZodObject<{}, z.core.$strip>;
|
|
63
|
+
export type TaskListInput = z.infer<typeof TaskListInputSchema>;
|
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";
|
|
@@ -53,7 +53,8 @@ var TmuxConfigSchema = z.object({
|
|
|
53
53
|
var ExperimentalConfigSchema = z.object({
|
|
54
54
|
plugin_load_timeout_ms: z.number().min(1000).optional(),
|
|
55
55
|
context_window_warning_threshold: z.number().min(0).max(1).optional(),
|
|
56
|
-
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)
|
|
57
58
|
});
|
|
58
59
|
var DelegationTriggerSchema = z.object({
|
|
59
60
|
domain: z.string(),
|
|
@@ -1247,6 +1248,8 @@ Use this structure:
|
|
|
1247
1248
|
\`\`\`
|
|
1248
1249
|
|
|
1249
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.
|
|
1250
1253
|
</PlanOutput>
|
|
1251
1254
|
|
|
1252
1255
|
<Constraints>
|
|
@@ -1909,7 +1912,10 @@ var KNOWN_TOOL_NAMES = new Set([
|
|
|
1909
1912
|
"call_weave_agent",
|
|
1910
1913
|
"webfetch",
|
|
1911
1914
|
"todowrite",
|
|
1912
|
-
"skill"
|
|
1915
|
+
"skill",
|
|
1916
|
+
"task_create",
|
|
1917
|
+
"task_update",
|
|
1918
|
+
"task_list"
|
|
1913
1919
|
]);
|
|
1914
1920
|
var AGENT_NAME_PATTERN = /^[a-z][a-z0-9_-]*$/;
|
|
1915
1921
|
function parseFallbackModels(models) {
|
|
@@ -2289,6 +2295,275 @@ function createSkillResolver(discovered) {
|
|
|
2289
2295
|
return resolveMultipleSkills(skillNames, disabledSkills, discovered);
|
|
2290
2296
|
};
|
|
2291
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
|
+
}
|
|
2292
2567
|
// src/create-tools.ts
|
|
2293
2568
|
async function createTools(options) {
|
|
2294
2569
|
const { ctx, pluginConfig } = options;
|
|
@@ -2299,6 +2574,13 @@ async function createTools(options) {
|
|
|
2299
2574
|
});
|
|
2300
2575
|
const resolveSkillsFn = createSkillResolver(skillResult);
|
|
2301
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
|
+
}
|
|
2302
2584
|
return {
|
|
2303
2585
|
tools,
|
|
2304
2586
|
availableSkills: skillResult.skills,
|
|
@@ -2491,17 +2773,17 @@ var WORK_STATE_FILE = "state.json";
|
|
|
2491
2773
|
var WORK_STATE_PATH = `${WEAVE_DIR}/${WORK_STATE_FILE}`;
|
|
2492
2774
|
var PLANS_DIR = `${WEAVE_DIR}/plans`;
|
|
2493
2775
|
// src/features/work-state/storage.ts
|
|
2494
|
-
import { existsSync as existsSync7, readFileSync as
|
|
2495
|
-
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";
|
|
2496
2778
|
import { execSync } from "child_process";
|
|
2497
2779
|
var UNCHECKED_RE = /^[-*]\s*\[\s*\]/gm;
|
|
2498
2780
|
var CHECKED_RE = /^[-*]\s*\[[xX]\]/gm;
|
|
2499
2781
|
function readWorkState(directory) {
|
|
2500
|
-
const filePath =
|
|
2782
|
+
const filePath = join7(directory, WEAVE_DIR, WORK_STATE_FILE);
|
|
2501
2783
|
try {
|
|
2502
2784
|
if (!existsSync7(filePath))
|
|
2503
2785
|
return null;
|
|
2504
|
-
const raw =
|
|
2786
|
+
const raw = readFileSync6(filePath, "utf-8");
|
|
2505
2787
|
const parsed = JSON.parse(raw);
|
|
2506
2788
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
2507
2789
|
return null;
|
|
@@ -2517,21 +2799,21 @@ function readWorkState(directory) {
|
|
|
2517
2799
|
}
|
|
2518
2800
|
function writeWorkState(directory, state) {
|
|
2519
2801
|
try {
|
|
2520
|
-
const dir =
|
|
2802
|
+
const dir = join7(directory, WEAVE_DIR);
|
|
2521
2803
|
if (!existsSync7(dir)) {
|
|
2522
|
-
|
|
2804
|
+
mkdirSync3(dir, { recursive: true });
|
|
2523
2805
|
}
|
|
2524
|
-
|
|
2806
|
+
writeFileSync2(join7(dir, WORK_STATE_FILE), JSON.stringify(state, null, 2), "utf-8");
|
|
2525
2807
|
return true;
|
|
2526
2808
|
} catch {
|
|
2527
2809
|
return false;
|
|
2528
2810
|
}
|
|
2529
2811
|
}
|
|
2530
2812
|
function clearWorkState(directory) {
|
|
2531
|
-
const filePath =
|
|
2813
|
+
const filePath = join7(directory, WEAVE_DIR, WORK_STATE_FILE);
|
|
2532
2814
|
try {
|
|
2533
2815
|
if (existsSync7(filePath)) {
|
|
2534
|
-
|
|
2816
|
+
unlinkSync2(filePath);
|
|
2535
2817
|
}
|
|
2536
2818
|
return true;
|
|
2537
2819
|
} catch {
|
|
@@ -2572,13 +2854,13 @@ function getHeadSha(directory) {
|
|
|
2572
2854
|
}
|
|
2573
2855
|
}
|
|
2574
2856
|
function findPlans(directory) {
|
|
2575
|
-
const plansDir =
|
|
2857
|
+
const plansDir = join7(directory, PLANS_DIR);
|
|
2576
2858
|
try {
|
|
2577
2859
|
if (!existsSync7(plansDir))
|
|
2578
2860
|
return [];
|
|
2579
|
-
const files =
|
|
2580
|
-
const fullPath =
|
|
2581
|
-
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);
|
|
2582
2864
|
return { path: fullPath, mtime: stat.mtimeMs };
|
|
2583
2865
|
}).sort((a, b) => b.mtime - a.mtime).map((f) => f.path);
|
|
2584
2866
|
return files;
|
|
@@ -2591,7 +2873,7 @@ function getPlanProgress(planPath) {
|
|
|
2591
2873
|
return { total: 0, completed: 0, isComplete: true };
|
|
2592
2874
|
}
|
|
2593
2875
|
try {
|
|
2594
|
-
const content =
|
|
2876
|
+
const content = readFileSync6(planPath, "utf-8");
|
|
2595
2877
|
const unchecked = content.match(UNCHECKED_RE) || [];
|
|
2596
2878
|
const checked = content.match(CHECKED_RE) || [];
|
|
2597
2879
|
const total = unchecked.length + checked.length;
|
|
@@ -2606,7 +2888,7 @@ function getPlanProgress(planPath) {
|
|
|
2606
2888
|
}
|
|
2607
2889
|
}
|
|
2608
2890
|
function getPlanName(planPath) {
|
|
2609
|
-
return
|
|
2891
|
+
return basename2(planPath, ".md");
|
|
2610
2892
|
}
|
|
2611
2893
|
function pauseWork(directory) {
|
|
2612
2894
|
const state = readWorkState(directory);
|
|
@@ -2623,7 +2905,7 @@ function resumeWork(directory) {
|
|
|
2623
2905
|
return writeWorkState(directory, state);
|
|
2624
2906
|
}
|
|
2625
2907
|
// src/features/work-state/validation.ts
|
|
2626
|
-
import { readFileSync as
|
|
2908
|
+
import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
|
|
2627
2909
|
import { resolve as resolve3, sep as sep2 } from "path";
|
|
2628
2910
|
function validatePlan(planPath, projectDir) {
|
|
2629
2911
|
const errors = [];
|
|
@@ -2646,13 +2928,13 @@ function validatePlan(planPath, projectDir) {
|
|
|
2646
2928
|
});
|
|
2647
2929
|
return { valid: false, errors, warnings };
|
|
2648
2930
|
}
|
|
2649
|
-
const content =
|
|
2931
|
+
const content = readFileSync7(resolvedPlanPath, "utf-8");
|
|
2650
2932
|
validateStructure(content, errors, warnings);
|
|
2651
2933
|
validateCheckboxes(content, errors, warnings);
|
|
2652
2934
|
validateFileReferences(content, projectDir, warnings);
|
|
2653
2935
|
validateNumbering(content, errors, warnings);
|
|
2654
2936
|
validateEffortEstimate(content, warnings);
|
|
2655
|
-
validateVerificationSection(content,
|
|
2937
|
+
validateVerificationSection(content, warnings);
|
|
2656
2938
|
return {
|
|
2657
2939
|
valid: errors.length === 0,
|
|
2658
2940
|
errors,
|
|
@@ -2684,15 +2966,15 @@ function hasSection(content, heading) {
|
|
|
2684
2966
|
return content.split(`
|
|
2685
2967
|
`).some((line) => line.trim() === heading);
|
|
2686
2968
|
}
|
|
2687
|
-
function validateStructure(content,
|
|
2688
|
-
const
|
|
2689
|
-
["## TL;DR", "Missing
|
|
2690
|
-
["## TODOs", "Missing
|
|
2691
|
-
["## 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"]
|
|
2692
2974
|
];
|
|
2693
|
-
for (const [heading, message] of
|
|
2975
|
+
for (const [heading, message] of expectedSections) {
|
|
2694
2976
|
if (!hasSection(content, heading)) {
|
|
2695
|
-
|
|
2977
|
+
warnings.push({ severity: "warning", category: "structure", message });
|
|
2696
2978
|
}
|
|
2697
2979
|
}
|
|
2698
2980
|
const optionalSections = [
|
|
@@ -2708,6 +2990,14 @@ function validateStructure(content, errors, warnings) {
|
|
|
2708
2990
|
function validateCheckboxes(content, errors, warnings) {
|
|
2709
2991
|
const todosSection = extractSection(content, "## TODOs");
|
|
2710
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
|
+
}
|
|
2711
3001
|
return;
|
|
2712
3002
|
}
|
|
2713
3003
|
const checkboxPattern = /^- \[[ x]\] /m;
|
|
@@ -2888,17 +3178,17 @@ function validateEffortEstimate(content, warnings) {
|
|
|
2888
3178
|
});
|
|
2889
3179
|
}
|
|
2890
3180
|
}
|
|
2891
|
-
function validateVerificationSection(content,
|
|
3181
|
+
function validateVerificationSection(content, warnings) {
|
|
2892
3182
|
const verificationSection = extractSection(content, "## Verification");
|
|
2893
3183
|
if (verificationSection === null) {
|
|
2894
3184
|
return;
|
|
2895
3185
|
}
|
|
2896
3186
|
const hasCheckbox = /^- \[[ x]\] /m.test(verificationSection);
|
|
2897
3187
|
if (!hasCheckbox) {
|
|
2898
|
-
|
|
2899
|
-
severity: "
|
|
3188
|
+
warnings.push({
|
|
3189
|
+
severity: "warning",
|
|
2900
3190
|
category: "verification",
|
|
2901
|
-
message: "## Verification section contains no checkboxes —
|
|
3191
|
+
message: "## Verification section contains no checkboxes — consider adding verifiable conditions"
|
|
2902
3192
|
});
|
|
2903
3193
|
}
|
|
2904
3194
|
}
|
|
@@ -2909,8 +3199,8 @@ var ACTIVE_INSTANCE_FILE = "active-instance.json";
|
|
|
2909
3199
|
var WORKFLOWS_DIR_PROJECT = ".opencode/workflows";
|
|
2910
3200
|
var WORKFLOWS_DIR_USER = "workflows";
|
|
2911
3201
|
// src/features/workflow/storage.ts
|
|
2912
|
-
import { existsSync as existsSync9, readFileSync as
|
|
2913
|
-
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";
|
|
2914
3204
|
import { randomBytes } from "node:crypto";
|
|
2915
3205
|
function generateInstanceId() {
|
|
2916
3206
|
return `wf_${randomBytes(4).toString("hex")}`;
|
|
@@ -2946,11 +3236,11 @@ function createWorkflowInstance(definition, definitionPath, goal, sessionId) {
|
|
|
2946
3236
|
};
|
|
2947
3237
|
}
|
|
2948
3238
|
function readWorkflowInstance(directory, instanceId) {
|
|
2949
|
-
const filePath =
|
|
3239
|
+
const filePath = join8(directory, WORKFLOWS_STATE_DIR, instanceId, INSTANCE_STATE_FILE);
|
|
2950
3240
|
try {
|
|
2951
3241
|
if (!existsSync9(filePath))
|
|
2952
3242
|
return null;
|
|
2953
|
-
const raw =
|
|
3243
|
+
const raw = readFileSync8(filePath, "utf-8");
|
|
2954
3244
|
const parsed = JSON.parse(raw);
|
|
2955
3245
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
2956
3246
|
return null;
|
|
@@ -2963,22 +3253,22 @@ function readWorkflowInstance(directory, instanceId) {
|
|
|
2963
3253
|
}
|
|
2964
3254
|
function writeWorkflowInstance(directory, instance) {
|
|
2965
3255
|
try {
|
|
2966
|
-
const dir =
|
|
3256
|
+
const dir = join8(directory, WORKFLOWS_STATE_DIR, instance.instance_id);
|
|
2967
3257
|
if (!existsSync9(dir)) {
|
|
2968
|
-
|
|
3258
|
+
mkdirSync4(dir, { recursive: true });
|
|
2969
3259
|
}
|
|
2970
|
-
|
|
3260
|
+
writeFileSync3(join8(dir, INSTANCE_STATE_FILE), JSON.stringify(instance, null, 2), "utf-8");
|
|
2971
3261
|
return true;
|
|
2972
3262
|
} catch {
|
|
2973
3263
|
return false;
|
|
2974
3264
|
}
|
|
2975
3265
|
}
|
|
2976
3266
|
function readActiveInstance(directory) {
|
|
2977
|
-
const filePath =
|
|
3267
|
+
const filePath = join8(directory, WORKFLOWS_STATE_DIR, ACTIVE_INSTANCE_FILE);
|
|
2978
3268
|
try {
|
|
2979
3269
|
if (!existsSync9(filePath))
|
|
2980
3270
|
return null;
|
|
2981
|
-
const raw =
|
|
3271
|
+
const raw = readFileSync8(filePath, "utf-8");
|
|
2982
3272
|
const parsed = JSON.parse(raw);
|
|
2983
3273
|
if (!parsed || typeof parsed !== "object" || typeof parsed.instance_id !== "string")
|
|
2984
3274
|
return null;
|
|
@@ -2989,22 +3279,22 @@ function readActiveInstance(directory) {
|
|
|
2989
3279
|
}
|
|
2990
3280
|
function setActiveInstance(directory, instanceId) {
|
|
2991
3281
|
try {
|
|
2992
|
-
const dir =
|
|
3282
|
+
const dir = join8(directory, WORKFLOWS_STATE_DIR);
|
|
2993
3283
|
if (!existsSync9(dir)) {
|
|
2994
|
-
|
|
3284
|
+
mkdirSync4(dir, { recursive: true });
|
|
2995
3285
|
}
|
|
2996
3286
|
const pointer = { instance_id: instanceId };
|
|
2997
|
-
|
|
3287
|
+
writeFileSync3(join8(dir, ACTIVE_INSTANCE_FILE), JSON.stringify(pointer, null, 2), "utf-8");
|
|
2998
3288
|
return true;
|
|
2999
3289
|
} catch {
|
|
3000
3290
|
return false;
|
|
3001
3291
|
}
|
|
3002
3292
|
}
|
|
3003
3293
|
function clearActiveInstance(directory) {
|
|
3004
|
-
const filePath =
|
|
3294
|
+
const filePath = join8(directory, WORKFLOWS_STATE_DIR, ACTIVE_INSTANCE_FILE);
|
|
3005
3295
|
try {
|
|
3006
3296
|
if (existsSync9(filePath)) {
|
|
3007
|
-
|
|
3297
|
+
unlinkSync3(filePath);
|
|
3008
3298
|
}
|
|
3009
3299
|
return true;
|
|
3010
3300
|
} catch {
|
|
@@ -3024,35 +3314,35 @@ import * as os3 from "os";
|
|
|
3024
3314
|
import { parse as parseJsonc } from "jsonc-parser";
|
|
3025
3315
|
|
|
3026
3316
|
// src/features/workflow/schema.ts
|
|
3027
|
-
import { z as
|
|
3028
|
-
var CompletionConfigSchema =
|
|
3029
|
-
method:
|
|
3030
|
-
plan_name:
|
|
3031
|
-
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()
|
|
3032
3322
|
});
|
|
3033
|
-
var ArtifactRefSchema =
|
|
3034
|
-
name:
|
|
3035
|
-
description:
|
|
3323
|
+
var ArtifactRefSchema = z3.object({
|
|
3324
|
+
name: z3.string(),
|
|
3325
|
+
description: z3.string().optional()
|
|
3036
3326
|
});
|
|
3037
|
-
var StepArtifactsSchema =
|
|
3038
|
-
inputs:
|
|
3039
|
-
outputs:
|
|
3327
|
+
var StepArtifactsSchema = z3.object({
|
|
3328
|
+
inputs: z3.array(ArtifactRefSchema).optional(),
|
|
3329
|
+
outputs: z3.array(ArtifactRefSchema).optional()
|
|
3040
3330
|
});
|
|
3041
|
-
var WorkflowStepSchema =
|
|
3042
|
-
id:
|
|
3043
|
-
name:
|
|
3044
|
-
type:
|
|
3045
|
-
agent:
|
|
3046
|
-
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(),
|
|
3047
3337
|
completion: CompletionConfigSchema,
|
|
3048
3338
|
artifacts: StepArtifactsSchema.optional(),
|
|
3049
|
-
on_reject:
|
|
3339
|
+
on_reject: z3.enum(["pause", "fail"]).optional()
|
|
3050
3340
|
});
|
|
3051
|
-
var WorkflowDefinitionSchema =
|
|
3052
|
-
name:
|
|
3053
|
-
description:
|
|
3054
|
-
version:
|
|
3055
|
-
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")
|
|
3056
3346
|
});
|
|
3057
3347
|
|
|
3058
3348
|
// src/features/workflow/discovery.ts
|
|
@@ -3194,7 +3484,7 @@ function truncateSummary(text) {
|
|
|
3194
3484
|
}
|
|
3195
3485
|
// src/features/workflow/completion.ts
|
|
3196
3486
|
import { existsSync as existsSync11 } from "fs";
|
|
3197
|
-
import { join as
|
|
3487
|
+
import { join as join10 } from "path";
|
|
3198
3488
|
var DEFAULT_CONFIRM_KEYWORDS = ["confirmed", "approved", "continue", "done", "let's proceed", "looks good", "lgtm"];
|
|
3199
3489
|
var VERDICT_APPROVE_RE = /\[\s*APPROVE\s*\]/i;
|
|
3200
3490
|
var VERDICT_REJECT_RE = /\[\s*REJECT\s*\]/i;
|
|
@@ -3246,7 +3536,7 @@ function checkPlanCreated(context) {
|
|
|
3246
3536
|
summary: `Plan created at ${matchingPlan}`
|
|
3247
3537
|
};
|
|
3248
3538
|
}
|
|
3249
|
-
const directPath =
|
|
3539
|
+
const directPath = join10(directory, ".weave", "plans", `${planName}.md`);
|
|
3250
3540
|
if (existsSync11(directPath)) {
|
|
3251
3541
|
return {
|
|
3252
3542
|
complete: true,
|
|
@@ -3262,7 +3552,7 @@ function checkPlanComplete(context) {
|
|
|
3262
3552
|
if (!planName) {
|
|
3263
3553
|
return { complete: false, reason: "plan_complete requires plan_name in completion config" };
|
|
3264
3554
|
}
|
|
3265
|
-
const planPath =
|
|
3555
|
+
const planPath = join10(directory, ".weave", "plans", `${planName}.md`);
|
|
3266
3556
|
if (!existsSync11(planPath)) {
|
|
3267
3557
|
return { complete: false, reason: `Plan file not found: ${planPath}` };
|
|
3268
3558
|
}
|
|
@@ -4224,8 +4514,8 @@ function clearSession2(sessionId) {
|
|
|
4224
4514
|
sessionMap.delete(sessionId);
|
|
4225
4515
|
}
|
|
4226
4516
|
// src/features/analytics/storage.ts
|
|
4227
|
-
import { existsSync as existsSync12, mkdirSync as
|
|
4228
|
-
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";
|
|
4229
4519
|
|
|
4230
4520
|
// src/features/analytics/types.ts
|
|
4231
4521
|
var ANALYTICS_DIR = ".weave/analytics";
|
|
@@ -4240,30 +4530,30 @@ function zeroTokenUsage() {
|
|
|
4240
4530
|
// src/features/analytics/storage.ts
|
|
4241
4531
|
var MAX_SESSION_ENTRIES = 1000;
|
|
4242
4532
|
function ensureAnalyticsDir(directory) {
|
|
4243
|
-
const dir =
|
|
4244
|
-
|
|
4533
|
+
const dir = join11(directory, ANALYTICS_DIR);
|
|
4534
|
+
mkdirSync5(dir, { recursive: true, mode: 448 });
|
|
4245
4535
|
return dir;
|
|
4246
4536
|
}
|
|
4247
4537
|
function appendSessionSummary(directory, summary) {
|
|
4248
4538
|
try {
|
|
4249
4539
|
const dir = ensureAnalyticsDir(directory);
|
|
4250
|
-
const filePath =
|
|
4540
|
+
const filePath = join11(dir, SESSION_SUMMARIES_FILE);
|
|
4251
4541
|
const line = JSON.stringify(summary) + `
|
|
4252
4542
|
`;
|
|
4253
4543
|
appendFileSync2(filePath, line, { encoding: "utf-8", mode: 384 });
|
|
4254
4544
|
try {
|
|
4255
4545
|
const TYPICAL_ENTRY_BYTES = 200;
|
|
4256
4546
|
const rotationSizeThreshold = MAX_SESSION_ENTRIES * TYPICAL_ENTRY_BYTES * 0.9;
|
|
4257
|
-
const { size } =
|
|
4547
|
+
const { size } = statSync3(filePath);
|
|
4258
4548
|
if (size > rotationSizeThreshold) {
|
|
4259
|
-
const content =
|
|
4549
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
4260
4550
|
const lines = content.split(`
|
|
4261
4551
|
`).filter((l) => l.trim().length > 0);
|
|
4262
4552
|
if (lines.length > MAX_SESSION_ENTRIES) {
|
|
4263
4553
|
const trimmed = lines.slice(-MAX_SESSION_ENTRIES).join(`
|
|
4264
4554
|
`) + `
|
|
4265
4555
|
`;
|
|
4266
|
-
|
|
4556
|
+
writeFileSync4(filePath, trimmed, { encoding: "utf-8", mode: 384 });
|
|
4267
4557
|
}
|
|
4268
4558
|
}
|
|
4269
4559
|
} catch {}
|
|
@@ -4273,11 +4563,11 @@ function appendSessionSummary(directory, summary) {
|
|
|
4273
4563
|
}
|
|
4274
4564
|
}
|
|
4275
4565
|
function readSessionSummaries(directory) {
|
|
4276
|
-
const filePath =
|
|
4566
|
+
const filePath = join11(directory, ANALYTICS_DIR, SESSION_SUMMARIES_FILE);
|
|
4277
4567
|
try {
|
|
4278
4568
|
if (!existsSync12(filePath))
|
|
4279
4569
|
return [];
|
|
4280
|
-
const content =
|
|
4570
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
4281
4571
|
const lines = content.split(`
|
|
4282
4572
|
`).filter((line) => line.trim().length > 0);
|
|
4283
4573
|
const summaries = [];
|
|
@@ -4294,19 +4584,19 @@ function readSessionSummaries(directory) {
|
|
|
4294
4584
|
function writeFingerprint(directory, fingerprint) {
|
|
4295
4585
|
try {
|
|
4296
4586
|
const dir = ensureAnalyticsDir(directory);
|
|
4297
|
-
const filePath =
|
|
4298
|
-
|
|
4587
|
+
const filePath = join11(dir, FINGERPRINT_FILE);
|
|
4588
|
+
writeFileSync4(filePath, JSON.stringify(fingerprint, null, 2), { encoding: "utf-8", mode: 384 });
|
|
4299
4589
|
return true;
|
|
4300
4590
|
} catch {
|
|
4301
4591
|
return false;
|
|
4302
4592
|
}
|
|
4303
4593
|
}
|
|
4304
4594
|
function readFingerprint(directory) {
|
|
4305
|
-
const filePath =
|
|
4595
|
+
const filePath = join11(directory, ANALYTICS_DIR, FINGERPRINT_FILE);
|
|
4306
4596
|
try {
|
|
4307
4597
|
if (!existsSync12(filePath))
|
|
4308
4598
|
return null;
|
|
4309
|
-
const content =
|
|
4599
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
4310
4600
|
const parsed = JSON.parse(content);
|
|
4311
4601
|
if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.stack))
|
|
4312
4602
|
return null;
|
|
@@ -4318,23 +4608,23 @@ function readFingerprint(directory) {
|
|
|
4318
4608
|
function writeMetricsReport(directory, report) {
|
|
4319
4609
|
try {
|
|
4320
4610
|
const dir = ensureAnalyticsDir(directory);
|
|
4321
|
-
const filePath =
|
|
4611
|
+
const filePath = join11(dir, METRICS_REPORTS_FILE);
|
|
4322
4612
|
const line = JSON.stringify(report) + `
|
|
4323
4613
|
`;
|
|
4324
4614
|
appendFileSync2(filePath, line, { encoding: "utf-8", mode: 384 });
|
|
4325
4615
|
try {
|
|
4326
4616
|
const TYPICAL_ENTRY_BYTES = 200;
|
|
4327
4617
|
const rotationSizeThreshold = MAX_METRICS_ENTRIES * TYPICAL_ENTRY_BYTES * 0.9;
|
|
4328
|
-
const { size } =
|
|
4618
|
+
const { size } = statSync3(filePath);
|
|
4329
4619
|
if (size > rotationSizeThreshold) {
|
|
4330
|
-
const content =
|
|
4620
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
4331
4621
|
const lines = content.split(`
|
|
4332
4622
|
`).filter((l) => l.trim().length > 0);
|
|
4333
4623
|
if (lines.length > MAX_METRICS_ENTRIES) {
|
|
4334
4624
|
const trimmed = lines.slice(-MAX_METRICS_ENTRIES).join(`
|
|
4335
4625
|
`) + `
|
|
4336
4626
|
`;
|
|
4337
|
-
|
|
4627
|
+
writeFileSync4(filePath, trimmed, { encoding: "utf-8", mode: 384 });
|
|
4338
4628
|
}
|
|
4339
4629
|
}
|
|
4340
4630
|
} catch {}
|
|
@@ -4344,11 +4634,11 @@ function writeMetricsReport(directory, report) {
|
|
|
4344
4634
|
}
|
|
4345
4635
|
}
|
|
4346
4636
|
function readMetricsReports(directory) {
|
|
4347
|
-
const filePath =
|
|
4637
|
+
const filePath = join11(directory, ANALYTICS_DIR, METRICS_REPORTS_FILE);
|
|
4348
4638
|
try {
|
|
4349
4639
|
if (!existsSync12(filePath))
|
|
4350
4640
|
return [];
|
|
4351
|
-
const content =
|
|
4641
|
+
const content = readFileSync10(filePath, "utf-8");
|
|
4352
4642
|
const lines = content.split(`
|
|
4353
4643
|
`).filter((line) => line.trim().length > 0);
|
|
4354
4644
|
const reports = [];
|
|
@@ -4509,7 +4799,7 @@ function topTools(summaries, limit = 5) {
|
|
|
4509
4799
|
counts[t.tool] = (counts[t.tool] ?? 0) + t.count;
|
|
4510
4800
|
}
|
|
4511
4801
|
}
|
|
4512
|
-
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);
|
|
4513
4803
|
}
|
|
4514
4804
|
function formatMetricsMarkdown(reports, summaries, args) {
|
|
4515
4805
|
if (reports.length === 0 && summaries.length === 0) {
|
|
@@ -4574,7 +4864,7 @@ function formatMetricsMarkdown(reports, summaries, args) {
|
|
|
4574
4864
|
}
|
|
4575
4865
|
|
|
4576
4866
|
// src/features/analytics/plan-parser.ts
|
|
4577
|
-
import { readFileSync as
|
|
4867
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
4578
4868
|
function extractSection2(content, heading) {
|
|
4579
4869
|
const lines = content.split(`
|
|
4580
4870
|
`);
|
|
@@ -4609,7 +4899,7 @@ function extractFilePath2(raw) {
|
|
|
4609
4899
|
function extractPlannedFiles(planPath) {
|
|
4610
4900
|
let content;
|
|
4611
4901
|
try {
|
|
4612
|
-
content =
|
|
4902
|
+
content = readFileSync11(planPath, "utf-8");
|
|
4613
4903
|
} catch {
|
|
4614
4904
|
return [];
|
|
4615
4905
|
}
|
|
@@ -4759,7 +5049,7 @@ function generateMetricsReport(directory, state) {
|
|
|
4759
5049
|
// src/plugin/plugin-interface.ts
|
|
4760
5050
|
var FINALIZE_TODOS_MARKER = "<!-- weave:finalize-todos -->";
|
|
4761
5051
|
function createPluginInterface(args) {
|
|
4762
|
-
const { pluginConfig, hooks, tools, configHandler, agents, client, directory = "", tracker } = args;
|
|
5052
|
+
const { pluginConfig, hooks, tools, configHandler, agents, client, directory = "", tracker, taskSystemEnabled = false } = args;
|
|
4763
5053
|
const lastAssistantMessageText = new Map;
|
|
4764
5054
|
const lastUserMessageText = new Map;
|
|
4765
5055
|
const todoFinalizedSessions = new Set;
|
|
@@ -4771,9 +5061,24 @@ function createPluginInterface(args) {
|
|
|
4771
5061
|
agents,
|
|
4772
5062
|
availableTools: []
|
|
4773
5063
|
});
|
|
4774
|
-
config.agent
|
|
4775
|
-
|
|
4776
|
-
|
|
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) {
|
|
4777
5082
|
config.default_agent = result.defaultAgent;
|
|
4778
5083
|
}
|
|
4779
5084
|
},
|
|
@@ -4845,7 +5150,7 @@ ${result.contextInjection}`;
|
|
|
4845
5150
|
`).trim() ?? "";
|
|
4846
5151
|
if (userText && sessionID) {
|
|
4847
5152
|
lastUserMessageText.set(sessionID, userText);
|
|
4848
|
-
if (!userText.includes(FINALIZE_TODOS_MARKER)) {
|
|
5153
|
+
if (!taskSystemEnabled && !userText.includes(FINALIZE_TODOS_MARKER)) {
|
|
4849
5154
|
todoFinalizedSessions.delete(sessionID);
|
|
4850
5155
|
}
|
|
4851
5156
|
}
|
|
@@ -4882,7 +5187,7 @@ ${cmdResult.contextInjection}`;
|
|
|
4882
5187
|
const isStartWork = promptText.includes("<session-context>");
|
|
4883
5188
|
const isContinuation = promptText.includes(CONTINUATION_MARKER);
|
|
4884
5189
|
const isWorkflowContinuation = promptText.includes(WORKFLOW_CONTINUATION_MARKER);
|
|
4885
|
-
const isTodoFinalize = promptText.includes(FINALIZE_TODOS_MARKER);
|
|
5190
|
+
const isTodoFinalize = !taskSystemEnabled && promptText.includes(FINALIZE_TODOS_MARKER);
|
|
4886
5191
|
const isActiveWorkflow = (() => {
|
|
4887
5192
|
const wf = getActiveWorkflowInstance(directory);
|
|
4888
5193
|
return wf != null && wf.status === "running";
|
|
@@ -5066,7 +5371,7 @@ ${cmdResult.contextInjection}`;
|
|
|
5066
5371
|
}
|
|
5067
5372
|
}
|
|
5068
5373
|
}
|
|
5069
|
-
if (event.type === "session.idle" && client && !continuationFired) {
|
|
5374
|
+
if (event.type === "session.idle" && client && !continuationFired && !taskSystemEnabled) {
|
|
5070
5375
|
const evt = event;
|
|
5071
5376
|
const sessionId = evt.properties?.sessionID ?? "";
|
|
5072
5377
|
if (sessionId && !todoFinalizedSessions.has(sessionId)) {
|
|
@@ -5182,14 +5487,14 @@ Use todowrite NOW to mark all of them as "completed" (or "cancelled" if abandone
|
|
|
5182
5487
|
};
|
|
5183
5488
|
}
|
|
5184
5489
|
// src/features/analytics/fingerprint.ts
|
|
5185
|
-
import { existsSync as existsSync13, readFileSync as
|
|
5186
|
-
import { join as
|
|
5490
|
+
import { existsSync as existsSync13, readFileSync as readFileSync13, readdirSync as readdirSync6 } from "fs";
|
|
5491
|
+
import { join as join13 } from "path";
|
|
5187
5492
|
import { arch } from "os";
|
|
5188
5493
|
|
|
5189
5494
|
// src/shared/version.ts
|
|
5190
|
-
import { readFileSync as
|
|
5495
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
5191
5496
|
import { fileURLToPath } from "url";
|
|
5192
|
-
import { dirname as dirname2, join as
|
|
5497
|
+
import { dirname as dirname2, join as join12 } from "path";
|
|
5193
5498
|
var cachedVersion;
|
|
5194
5499
|
function getWeaveVersion() {
|
|
5195
5500
|
if (cachedVersion !== undefined)
|
|
@@ -5198,7 +5503,7 @@ function getWeaveVersion() {
|
|
|
5198
5503
|
const thisDir = dirname2(fileURLToPath(import.meta.url));
|
|
5199
5504
|
for (const rel of ["../../package.json", "../package.json"]) {
|
|
5200
5505
|
try {
|
|
5201
|
-
const pkg = JSON.parse(
|
|
5506
|
+
const pkg = JSON.parse(readFileSync12(join12(thisDir, rel), "utf-8"));
|
|
5202
5507
|
if (pkg.name === "@opencode_weave/weave" && typeof pkg.version === "string") {
|
|
5203
5508
|
const version = pkg.version;
|
|
5204
5509
|
cachedVersion = version;
|
|
@@ -5303,7 +5608,7 @@ function detectStack(directory) {
|
|
|
5303
5608
|
const detected = [];
|
|
5304
5609
|
for (const marker of STACK_MARKERS) {
|
|
5305
5610
|
for (const file of marker.files) {
|
|
5306
|
-
if (existsSync13(
|
|
5611
|
+
if (existsSync13(join13(directory, file))) {
|
|
5307
5612
|
detected.push({
|
|
5308
5613
|
name: marker.name,
|
|
5309
5614
|
confidence: marker.confidence,
|
|
@@ -5314,9 +5619,9 @@ function detectStack(directory) {
|
|
|
5314
5619
|
}
|
|
5315
5620
|
}
|
|
5316
5621
|
try {
|
|
5317
|
-
const pkgPath =
|
|
5622
|
+
const pkgPath = join13(directory, "package.json");
|
|
5318
5623
|
if (existsSync13(pkgPath)) {
|
|
5319
|
-
const pkg = JSON.parse(
|
|
5624
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
5320
5625
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
5321
5626
|
if (deps.react) {
|
|
5322
5627
|
detected.push({
|
|
@@ -5329,7 +5634,7 @@ function detectStack(directory) {
|
|
|
5329
5634
|
} catch {}
|
|
5330
5635
|
if (!detected.some((d) => d.name === "dotnet")) {
|
|
5331
5636
|
try {
|
|
5332
|
-
const entries =
|
|
5637
|
+
const entries = readdirSync6(directory);
|
|
5333
5638
|
const dotnetFile = entries.find((e) => e.endsWith(".csproj") || e.endsWith(".fsproj") || e.endsWith(".sln"));
|
|
5334
5639
|
if (dotnetFile) {
|
|
5335
5640
|
detected.push({
|
|
@@ -5349,27 +5654,27 @@ function detectStack(directory) {
|
|
|
5349
5654
|
});
|
|
5350
5655
|
}
|
|
5351
5656
|
function detectPackageManager(directory) {
|
|
5352
|
-
if (existsSync13(
|
|
5657
|
+
if (existsSync13(join13(directory, "bun.lockb")))
|
|
5353
5658
|
return "bun";
|
|
5354
|
-
if (existsSync13(
|
|
5659
|
+
if (existsSync13(join13(directory, "pnpm-lock.yaml")))
|
|
5355
5660
|
return "pnpm";
|
|
5356
|
-
if (existsSync13(
|
|
5661
|
+
if (existsSync13(join13(directory, "yarn.lock")))
|
|
5357
5662
|
return "yarn";
|
|
5358
|
-
if (existsSync13(
|
|
5663
|
+
if (existsSync13(join13(directory, "package-lock.json")))
|
|
5359
5664
|
return "npm";
|
|
5360
|
-
if (existsSync13(
|
|
5665
|
+
if (existsSync13(join13(directory, "package.json")))
|
|
5361
5666
|
return "npm";
|
|
5362
5667
|
return;
|
|
5363
5668
|
}
|
|
5364
5669
|
function detectMonorepo(directory) {
|
|
5365
5670
|
for (const marker of MONOREPO_MARKERS) {
|
|
5366
|
-
if (existsSync13(
|
|
5671
|
+
if (existsSync13(join13(directory, marker)))
|
|
5367
5672
|
return true;
|
|
5368
5673
|
}
|
|
5369
5674
|
try {
|
|
5370
|
-
const pkgPath =
|
|
5675
|
+
const pkgPath = join13(directory, "package.json");
|
|
5371
5676
|
if (existsSync13(pkgPath)) {
|
|
5372
|
-
const pkg = JSON.parse(
|
|
5677
|
+
const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
|
|
5373
5678
|
if (pkg.workspaces)
|
|
5374
5679
|
return true;
|
|
5375
5680
|
}
|
|
@@ -5527,7 +5832,7 @@ class SessionTracker {
|
|
|
5527
5832
|
const now = new Date;
|
|
5528
5833
|
const startedAt = new Date(session.startedAt);
|
|
5529
5834
|
const durationMs = now.getTime() - startedAt.getTime();
|
|
5530
|
-
const toolUsage = Object.entries(session.toolCounts).map(([
|
|
5835
|
+
const toolUsage = Object.entries(session.toolCounts).map(([tool4, count]) => ({ tool: tool4, count }));
|
|
5531
5836
|
const totalToolCalls = toolUsage.reduce((sum, entry) => sum + entry.count, 0);
|
|
5532
5837
|
const summary = {
|
|
5533
5838
|
sessionId,
|
|
@@ -5589,7 +5894,7 @@ var WeavePlugin = async (ctx) => {
|
|
|
5589
5894
|
const analyticsEnabled = pluginConfig.analytics?.enabled === true;
|
|
5590
5895
|
const fingerprintEnabled = analyticsEnabled && pluginConfig.analytics?.use_fingerprint === true;
|
|
5591
5896
|
const fingerprint = fingerprintEnabled ? getOrCreateFingerprint(ctx.directory) : null;
|
|
5592
|
-
const configDir =
|
|
5897
|
+
const configDir = join14(ctx.directory, ".opencode");
|
|
5593
5898
|
const toolsResult = await createTools({ ctx, pluginConfig });
|
|
5594
5899
|
const managers = createManagers({ ctx, pluginConfig, resolveSkills: toolsResult.resolveSkillsFn, fingerprint, configDir });
|
|
5595
5900
|
const hooks = createHooks({ pluginConfig, isHookEnabled, directory: ctx.directory, analyticsEnabled });
|
|
@@ -5602,7 +5907,8 @@ var WeavePlugin = async (ctx) => {
|
|
|
5602
5907
|
agents: managers.agents,
|
|
5603
5908
|
client: ctx.client,
|
|
5604
5909
|
directory: ctx.directory,
|
|
5605
|
-
tracker: analytics?.tracker
|
|
5910
|
+
tracker: analytics?.tracker,
|
|
5911
|
+
taskSystemEnabled: pluginConfig.experimental?.task_system !== false
|
|
5606
5912
|
});
|
|
5607
5913
|
};
|
|
5608
5914
|
var src_default = WeavePlugin;
|