@docyrus/docyrus 0.0.40 → 0.0.42

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.
Files changed (31) hide show
  1. package/README.md +7 -0
  2. package/main.js +3708 -1173
  3. package/main.js.map +4 -4
  4. package/package.json +8 -7
  5. package/resources/pi-agent/extensions/control.ts +0 -8
  6. package/resources/pi-agent/extensions/knowledge.ts +0 -7
  7. package/resources/pi-agent/extensions/loop.ts +1 -5
  8. package/resources/pi-agent/extensions/pi-bash-live-view/package.json +1 -1
  9. package/resources/pi-agent/extensions/pi-custom-compaction/events/register-events.ts +0 -10
  10. package/resources/pi-agent/extensions/pi-custom-compaction/package.json +4 -4
  11. package/resources/pi-agent/extensions/plan.ts +95 -4
  12. package/resources/pi-agent/extensions/prompt-editor.ts +0 -18
  13. package/resources/pi-agent/extensions/prompt-url-widget.ts +0 -4
  14. package/resources/pi-agent/extensions/review.ts +0 -4
  15. package/resources/pi-agent/extensions/tasks.ts +497 -0
  16. package/resources/pi-agent/extensions/todos.ts +102 -2
  17. package/resources/pi-agent/skills/agent-browser/SKILL.md +779 -0
  18. package/resources/pi-agent/skills/agent-browser/references/authentication.md +303 -0
  19. package/resources/pi-agent/skills/agent-browser/references/commands.md +295 -0
  20. package/resources/pi-agent/skills/agent-browser/references/profiling.md +120 -0
  21. package/resources/pi-agent/skills/agent-browser/references/proxy-support.md +194 -0
  22. package/resources/pi-agent/skills/agent-browser/references/session-management.md +193 -0
  23. package/resources/pi-agent/skills/agent-browser/references/snapshot-refs.md +219 -0
  24. package/resources/pi-agent/skills/agent-browser/references/video-recording.md +173 -0
  25. package/resources/pi-agent/skills/agent-browser/templates/authenticated-session.sh +105 -0
  26. package/resources/pi-agent/skills/agent-browser/templates/capture-workflow.sh +69 -0
  27. package/resources/pi-agent/skills/agent-browser/templates/form-automation.sh +62 -0
  28. package/resources/pi-agent/skills/docyrus-platform/references/docyrus-cli-usage.md +73 -0
  29. package/resources/pi-agent/skills/grill-me/SKILL.md +109 -0
  30. package/server-loader.js +24270 -3381
  31. package/server-loader.js.map +4 -4
@@ -65,6 +65,12 @@ const DEFAULT_TODO_SETTINGS = {
65
65
  };
66
66
  const LOCK_TTL_MS = 30 * 60 * 1000;
67
67
 
68
+ interface ITodoCliEnvironment {
69
+ executable: string;
70
+ entryPath: string;
71
+ scope: "local" | "global";
72
+ }
73
+
68
74
  interface TodoFrontMatter {
69
75
  id: string;
70
76
  title: string;
@@ -72,6 +78,7 @@ interface TodoFrontMatter {
72
78
  status: string;
73
79
  created_at: string;
74
80
  assigned_to_session?: string;
81
+ parent_task_id?: string;
75
82
  }
76
83
 
77
84
  interface TodoRecord extends TodoFrontMatter {
@@ -111,6 +118,7 @@ const TodoParams = Type.Object({
111
118
  body: Type.Optional(
112
119
  Type.String({ description: "Long-form details (markdown). Update replaces; append adds." }),
113
120
  ),
121
+ parentTaskId: Type.Optional(Type.String({ description: "Optional canonical project task id for linked local subtasks" })),
114
122
  force: Type.Optional(Type.Boolean({ description: "Override another session's assignment" })),
115
123
  });
116
124
 
@@ -150,6 +158,71 @@ function formatTodoId(id: string): string {
150
158
  return `${TODO_ID_PREFIX}${id}`;
151
159
  }
152
160
 
161
+ function readCliEnvironment(env: NodeJS.ProcessEnv = process.env): ITodoCliEnvironment {
162
+ const executable = env.DOCYRUS_CLI_EXECUTABLE?.trim();
163
+ const entryPath = env.DOCYRUS_CLI_ENTRY?.trim();
164
+ const scope = env.DOCYRUS_CLI_SCOPE?.trim() as "local" | "global" | undefined;
165
+
166
+ if (!executable || !entryPath || (scope !== "local" && scope !== "global")) {
167
+ throw new Error("Missing Docyrus CLI runtime env. Expected DOCYRUS_CLI_EXECUTABLE, DOCYRUS_CLI_ENTRY, and DOCYRUS_CLI_SCOPE.");
168
+ }
169
+
170
+ return {
171
+ executable,
172
+ entryPath,
173
+ scope,
174
+ };
175
+ }
176
+
177
+ async function runProjectPlanCliJson<TValue>(
178
+ environment: ITodoCliEnvironment,
179
+ pi: ExtensionAPI,
180
+ ctx: ExtensionContext,
181
+ args: string[],
182
+ ): Promise<TValue> {
183
+ const scopedArgs = environment.scope === "global" ? ["-g", ...args] : args;
184
+ const result = await pi.exec(environment.executable, [environment.entryPath, ...scopedArgs, "--json"], {
185
+ cwd: ctx.cwd,
186
+ });
187
+
188
+ const stdout = result.stdout?.toString().trim() || "";
189
+ const stderr = result.stderr?.toString().trim() || "";
190
+ const output = stdout || stderr;
191
+ if (result.code !== 0 || !output) {
192
+ throw new Error(output || `Command exited with code ${result.code ?? "unknown"}`);
193
+ }
194
+
195
+ return JSON.parse(output) as TValue;
196
+ }
197
+
198
+ async function validateParentTaskId(parentTaskId: string | undefined, pi: ExtensionAPI, ctx: ExtensionContext): Promise<string | undefined> {
199
+ const normalized = parentTaskId?.trim();
200
+ if (!normalized) {
201
+ return undefined;
202
+ }
203
+
204
+ const environment = readCliEnvironment();
205
+ const payload = await runProjectPlanCliJson<{
206
+ id?: string;
207
+ assignee?: string;
208
+ }>(environment, pi, ctx, [
209
+ "project-plan",
210
+ "get-task",
211
+ "--taskId",
212
+ normalized,
213
+ ]);
214
+
215
+ if (!payload.id) {
216
+ throw new Error(`Task "${normalized}" was not found.`);
217
+ }
218
+
219
+ if (payload.assignee !== "agent") {
220
+ throw new Error(`Task "${payload.id}" is assigned to "${payload.assignee}" and cannot own local agent subtasks.`);
221
+ }
222
+
223
+ return payload.id;
224
+ }
225
+
153
226
  function normalizeTodoId(id: string): string {
154
227
  let trimmed = id.trim();
155
228
  if (trimmed.startsWith("#")) {
@@ -198,7 +271,8 @@ function sortTodos(todos: TodoFrontMatter[]): TodoFrontMatter[] {
198
271
  function buildTodoSearchText(todo: TodoFrontMatter): string {
199
272
  const tags = todo.tags.join(" ");
200
273
  const assignment = todo.assigned_to_session ? `assigned:${todo.assigned_to_session}` : "";
201
- return `${formatTodoId(todo.id)} ${todo.id} ${todo.title} ${tags} ${todo.status} ${assignment}`.trim();
274
+ const parentTask = todo.parent_task_id ? `task:${todo.parent_task_id}` : "";
275
+ return `${formatTodoId(todo.id)} ${todo.id} ${todo.title} ${tags} ${todo.status} ${assignment} ${parentTask}`.trim();
202
276
  }
203
277
 
204
278
  function filterTodos(todos: TodoFrontMatter[], query: string): TodoFrontMatter[] {
@@ -375,6 +449,7 @@ class TodoSelectorComponent extends Container implements Focusable {
375
449
  const statusColor = closed ? "dim" : "success";
376
450
  const tagText = todo.tags.length ? ` [${todo.tags.join(", ")}]` : "";
377
451
  const assignmentText = renderAssignmentSuffix(this.theme, todo, this.currentSessionId);
452
+ const parentTaskText = todo.parent_task_id ? this.theme.fg("muted", ` ↳ ${todo.parent_task_id}`) : "";
378
453
  const line =
379
454
  prefix +
380
455
  this.theme.fg("accent", formatTodoId(todo.id)) +
@@ -382,6 +457,7 @@ class TodoSelectorComponent extends Container implements Focusable {
382
457
  this.theme.fg(titleColor, todo.title || "(untitled)") +
383
458
  this.theme.fg("muted", tagText) +
384
459
  assignmentText +
460
+ parentTaskText +
385
461
  " " +
386
462
  this.theme.fg(statusColor, `(${todo.status || "open"})`);
387
463
  this.listContainer.addChild(new Text(line, 0, 0));
@@ -798,6 +874,7 @@ function parseFrontMatter(text: string, idFallback: string): TodoFrontMatter {
798
874
  status: "open",
799
875
  created_at: "",
800
876
  assigned_to_session: undefined,
877
+ parent_task_id: undefined,
801
878
  };
802
879
 
803
880
  const trimmed = text.trim();
@@ -813,6 +890,9 @@ function parseFrontMatter(text: string, idFallback: string): TodoFrontMatter {
813
890
  if (typeof parsed.assigned_to_session === "string" && parsed.assigned_to_session.trim()) {
814
891
  data.assigned_to_session = parsed.assigned_to_session;
815
892
  }
893
+ if (typeof parsed.parent_task_id === "string" && parsed.parent_task_id.trim()) {
894
+ data.parent_task_id = parsed.parent_task_id.trim();
895
+ }
816
896
  if (Array.isArray(parsed.tags)) {
817
897
  data.tags = parsed.tags.filter((tag): tag is string => typeof tag === "string");
818
898
  }
@@ -890,6 +970,7 @@ function parseTodoContent(content: string, idFallback: string): TodoRecord {
890
970
  status: parsed.status,
891
971
  created_at: parsed.created_at,
892
972
  assigned_to_session: parsed.assigned_to_session,
973
+ parent_task_id: parsed.parent_task_id,
893
974
  body: body ?? "",
894
975
  };
895
976
  }
@@ -903,6 +984,7 @@ function serializeTodo(todo: TodoRecord): string {
903
984
  status: todo.status,
904
985
  created_at: todo.created_at,
905
986
  assigned_to_session: todo.assigned_to_session || undefined,
987
+ parent_task_id: todo.parent_task_id || undefined,
906
988
  },
907
989
  null,
908
990
  2,
@@ -1039,6 +1121,7 @@ async function listTodos(todosDir: string): Promise<TodoFrontMatter[]> {
1039
1121
  status: parsed.status,
1040
1122
  created_at: parsed.created_at,
1041
1123
  assigned_to_session: parsed.assigned_to_session,
1124
+ parent_task_id: parsed.parent_task_id,
1042
1125
  });
1043
1126
  } catch {
1044
1127
  // ignore unreadable todo
@@ -1072,6 +1155,7 @@ function listTodosSync(todosDir: string): TodoFrontMatter[] {
1072
1155
  status: parsed.status,
1073
1156
  created_at: parsed.created_at,
1074
1157
  assigned_to_session: parsed.assigned_to_session,
1158
+ parent_task_id: parsed.parent_task_id,
1075
1159
  });
1076
1160
  } catch {
1077
1161
  // ignore
@@ -1107,7 +1191,8 @@ function renderAssignmentSuffix(
1107
1191
 
1108
1192
  function formatTodoHeading(todo: TodoFrontMatter): string {
1109
1193
  const tagText = todo.tags.length ? ` [${todo.tags.join(", ")}]` : "";
1110
- return `${formatTodoId(todo.id)} ${getTodoTitle(todo)}${tagText}${formatAssignmentSuffix(todo)}`;
1194
+ const parentTaskText = todo.parent_task_id ? ` ↳ ${todo.parent_task_id}` : "";
1195
+ return `${formatTodoId(todo.id)} ${getTodoTitle(todo)}${tagText}${formatAssignmentSuffix(todo)}${parentTaskText}`;
1111
1196
  }
1112
1197
 
1113
1198
  function buildRefinePrompt(todoId: string, title: string): string {
@@ -1248,6 +1333,7 @@ function renderTodoDetail(theme: Theme, todo: TodoRecord, expanded: boolean): st
1248
1333
  theme.fg("muted", `Status: ${getTodoStatus(todo)}`),
1249
1334
  theme.fg("muted", `Tags: ${tags}`),
1250
1335
  theme.fg("muted", `Created: ${createdAt}`),
1336
+ ...(todo.parent_task_id ? [theme.fg("muted", `Parent task: ${todo.parent_task_id}`)] : []),
1251
1337
  "",
1252
1338
  theme.fg("muted", "Body:"),
1253
1339
  ...bodyLines.map((line) => theme.fg("text", ` ${line}`)),
@@ -1497,6 +1583,16 @@ export default function todosExtension(pi: ExtensionAPI) {
1497
1583
  details: { action: "create", error: "title required" },
1498
1584
  };
1499
1585
  }
1586
+ let parentTaskId: string | undefined;
1587
+ try {
1588
+ parentTaskId = await validateParentTaskId(params.parentTaskId, pi, ctx);
1589
+ } catch (error) {
1590
+ const message = error instanceof Error ? error.message : String(error);
1591
+ return {
1592
+ content: [{ type: "text", text: message }],
1593
+ details: { action: "create", error: message },
1594
+ };
1595
+ }
1500
1596
  await ensureTodosDir(todosDir);
1501
1597
  const id = await generateTodoId(todosDir);
1502
1598
  const filePath = getTodoPath(todosDir, id);
@@ -1506,6 +1602,7 @@ export default function todosExtension(pi: ExtensionAPI) {
1506
1602
  tags: params.tags ?? [],
1507
1603
  status: params.status ?? "open",
1508
1604
  created_at: new Date().toISOString(),
1605
+ parent_task_id: parentTaskId,
1509
1606
  body: params.body ?? "",
1510
1607
  };
1511
1608
 
@@ -1559,6 +1656,9 @@ export default function todosExtension(pi: ExtensionAPI) {
1559
1656
  if (params.status !== undefined) {existing.status = params.status;}
1560
1657
  if (params.tags !== undefined) {existing.tags = params.tags;}
1561
1658
  if (params.body !== undefined) {existing.body = params.body;}
1659
+ if (params.parentTaskId !== undefined) {
1660
+ existing.parent_task_id = await validateParentTaskId(params.parentTaskId, pi, ctx);
1661
+ }
1562
1662
  if (!existing.created_at) {existing.created_at = new Date().toISOString();}
1563
1663
  clearAssignmentIfClosed(existing);
1564
1664