@docyrus/docyrus 0.0.41 → 0.0.43

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 (36) hide show
  1. package/README.md +2 -0
  2. package/agent-loader.js +11 -2
  3. package/agent-loader.js.map +2 -2
  4. package/main.js +1823 -697
  5. package/main.js.map +4 -4
  6. package/package.json +8 -7
  7. package/resources/pi-agent/extensions/architect.ts +2 -2
  8. package/resources/pi-agent/extensions/control.ts +0 -8
  9. package/resources/pi-agent/extensions/knowledge.ts +0 -7
  10. package/resources/pi-agent/extensions/loop.ts +1 -5
  11. package/resources/pi-agent/extensions/pi-bash-live-view/package.json +1 -1
  12. package/resources/pi-agent/extensions/pi-custom-compaction/events/register-events.ts +0 -10
  13. package/resources/pi-agent/extensions/pi-custom-compaction/package.json +4 -4
  14. package/resources/pi-agent/extensions/pi-mcp-adapter/package.json +22 -22
  15. package/resources/pi-agent/extensions/plan.ts +183 -45
  16. package/resources/pi-agent/extensions/prompt-editor.ts +0 -18
  17. package/resources/pi-agent/extensions/prompt-url-widget.ts +0 -4
  18. package/resources/pi-agent/extensions/review.ts +0 -4
  19. package/resources/pi-agent/prompts/agent-system.md +12 -0
  20. package/resources/pi-agent/prompts/coder-system.md +18 -0
  21. package/resources/pi-agent/skills/agent-browser/SKILL.md +779 -0
  22. package/resources/pi-agent/skills/agent-browser/references/authentication.md +303 -0
  23. package/resources/pi-agent/skills/agent-browser/references/commands.md +295 -0
  24. package/resources/pi-agent/skills/agent-browser/references/profiling.md +120 -0
  25. package/resources/pi-agent/skills/agent-browser/references/proxy-support.md +194 -0
  26. package/resources/pi-agent/skills/agent-browser/references/session-management.md +193 -0
  27. package/resources/pi-agent/skills/agent-browser/references/snapshot-refs.md +219 -0
  28. package/resources/pi-agent/skills/agent-browser/references/video-recording.md +173 -0
  29. package/resources/pi-agent/skills/agent-browser/templates/authenticated-session.sh +105 -0
  30. package/resources/pi-agent/skills/agent-browser/templates/capture-workflow.sh +69 -0
  31. package/resources/pi-agent/skills/agent-browser/templates/form-automation.sh +62 -0
  32. package/resources/pi-agent/skills/docyrus-app-dev-react/SKILL.md +17 -13
  33. package/resources/pi-agent/skills/docyrus-platform/references/docyrus-cli-usage.md +73 -0
  34. package/resources/pi-agent/skills/grill-me/SKILL.md +109 -0
  35. package/server-loader.js +124 -33
  36. package/server-loader.js.map +4 -4
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@docyrus/docyrus",
3
- "version": "0.0.41",
3
+ "version": "0.0.43",
4
4
  "private": false,
5
5
  "description": "Docyrus API CLI",
6
6
  "main": "./main.js",
@@ -12,9 +12,9 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@clack/prompts": "^0.11.0",
15
- "@hono/node-server": "^1.14.1",
16
- "@mariozechner/pi-ai": "0.64.0",
17
- "@mariozechner/pi-coding-agent": "0.64.0",
15
+ "@hono/node-server": "^1.19.13",
16
+ "@mariozechner/pi-ai": "0.65.0",
17
+ "@mariozechner/pi-coding-agent": "0.65.0",
18
18
  "@modelcontextprotocol/ext-apps": "^1.2.2",
19
19
  "@modelcontextprotocol/sdk": "^1.25.1",
20
20
  "@mozilla/readability": "^0.6.0",
@@ -23,8 +23,8 @@
23
23
  "@repomix/tree-sitter-wasms": "^0.1.16",
24
24
  "@sinclair/typebox": "^0.34.48",
25
25
  "@xterm/headless": "^5.5.0",
26
- "diff": "^8.0.2",
27
- "hono": "^4.7.10",
26
+ "diff": "^8.0.4",
27
+ "hono": "^4.12.12",
28
28
  "ignore-walk": "^8.0.0",
29
29
  "incur": "^0.1.6",
30
30
  "jsdom": "^29.0.1",
@@ -40,13 +40,14 @@
40
40
  "strip-ansi": "^7.1.0",
41
41
  "turndown": "^7.2.2",
42
42
  "turndown-plugin-gfm": "^1.0.2",
43
- "undici": "^7.16.0",
43
+ "undici": "^7.24.0",
44
44
  "unified": "^11.0.5",
45
45
  "unist-util-visit": "^5.1.0",
46
46
  "web-tree-sitter": "0.25.10",
47
47
  "zod": "^4.3.6"
48
48
  },
49
49
  "devDependencies": {
50
+ "@types/node": "^24.12.2",
50
51
  "@types/react": "^19.1.13"
51
52
  },
52
53
  "engines": {
@@ -746,8 +746,8 @@ async function architectHandler(pi: ExtensionAPI, ctx: ExtensionCommandContext,
746
746
  }
747
747
 
748
748
  export default function architectExtension(pi: ExtensionAPI) {
749
- pi.registerCommand("architect", {
750
- description: "Analyze tenant data sources and plan Docyrus schema artifacts for an app idea",
749
+ pi.registerCommand("plan", {
750
+ description: "Discover tenant state and start a planning-only branch for an app idea or task",
751
751
  handler: async(args, ctx) => {
752
752
  await architectHandler(pi, ctx, args);
753
753
  },
@@ -1087,14 +1087,6 @@ export default function(pi: ExtensionAPI) {
1087
1087
  }
1088
1088
  });
1089
1089
 
1090
- pi.on("session_switch", async(_event, ctx) => {
1091
- await refreshServer(ctx);
1092
- });
1093
-
1094
- pi.on("session_fork", async(_event, ctx) => {
1095
- await refreshServer(ctx);
1096
- });
1097
-
1098
1090
  pi.on("session_shutdown", async() => {
1099
1091
  if (state.aliasTimer) {
1100
1092
  clearInterval(state.aliasTimer);
@@ -621,13 +621,6 @@ export default function(pi: ExtensionAPI) {
621
621
  });
622
622
  });
623
623
 
624
- pi.on("session_switch", async(_event, ctx) => {
625
- await refreshKnowledgeState(pi, ctx, {
626
- runCheck: false,
627
- allowReminder: true,
628
- });
629
- });
630
-
631
624
  pi.on("turn_end", async(_event: TurnEndEvent, ctx) => {
632
625
  await refreshKnowledgeState(pi, ctx, {
633
626
  runCheck: false,
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { Type } from "@sinclair/typebox";
10
10
  import { complete, type Api, type Model, type UserMessage } from "@mariozechner/pi-ai";
11
- import type { ExtensionAPI, ExtensionContext, SessionSwitchEvent } from "@mariozechner/pi-coding-agent";
11
+ import type { ExtensionAPI, ExtensionContext } from "@mariozechner/pi-coding-agent";
12
12
  import { compact } from "@mariozechner/pi-coding-agent";
13
13
  import { Container, type SelectItem, SelectList, Text } from "@mariozechner/pi-tui";
14
14
  import { DynamicBorder } from "@mariozechner/pi-coding-agent";
@@ -439,8 +439,4 @@ export default function loopExtension(pi: ExtensionAPI): void {
439
439
  pi.on("session_start", async(_event, ctx) => {
440
440
  await restoreLoopState(ctx);
441
441
  });
442
-
443
- pi.on("session_switch", async(_event: SessionSwitchEvent, ctx) => {
444
- await restoreLoopState(ctx);
445
- });
446
442
  }
@@ -40,7 +40,7 @@
40
40
  "test": "node --test tests/*.test.mjs"
41
41
  },
42
42
  "dependencies": {
43
- "@mariozechner/pi-coding-agent": "^0.64.0",
43
+ "@mariozechner/pi-coding-agent": "^0.65.0",
44
44
  "@sinclair/typebox": "^0.34.38",
45
45
  "@xterm/headless": "^5.5.0",
46
46
  "node-pty": "^1.0.0",
@@ -208,16 +208,6 @@ export function registerEvents(pi: ExtensionAPI, runtime: IRuntimeServices): voi
208
208
  initializeSessionStatus(ctx, runtime);
209
209
  });
210
210
 
211
- pi.on("session_switch", async(_event, ctx) => {
212
- runtime.clearSessionScopedState(ctx);
213
- initializeSessionStatus(ctx, runtime);
214
- });
215
-
216
- pi.on("session_fork", async(_event, ctx) => {
217
- runtime.clearSessionScopedState(ctx);
218
- initializeSessionStatus(ctx, runtime);
219
- });
220
-
221
211
  pi.on("session_tree", async(_event, ctx) => {
222
212
  runtime.clearSessionScopedState(ctx);
223
213
  initializeSessionStatus(ctx, runtime);
@@ -43,10 +43,10 @@
43
43
  "test": "tsx --test test/**/*.test.ts"
44
44
  },
45
45
  "devDependencies": {
46
- "@mariozechner/pi-agent-core": "^0.64.0",
47
- "@mariozechner/pi-ai": "^0.64.0",
48
- "@mariozechner/pi-coding-agent": "^0.64.0",
49
- "@mariozechner/pi-tui": "^0.64.0",
46
+ "@mariozechner/pi-agent-core": "^0.65.0",
47
+ "@mariozechner/pi-ai": "^0.65.0",
48
+ "@mariozechner/pi-coding-agent": "^0.65.0",
49
+ "@mariozechner/pi-tui": "^0.65.0",
50
50
  "tsx": "^4.20.5"
51
51
  },
52
52
  "pi": {
@@ -2,16 +2,6 @@
2
2
  "name": "pi-mcp-adapter",
3
3
  "version": "2.2.0",
4
4
  "description": "MCP (Model Context Protocol) adapter extension for Pi coding agent",
5
- "type": "module",
6
- "license": "MIT",
7
- "author": "Nico Bailon",
8
- "bin": {
9
- "pi-mcp-adapter": "./cli.js"
10
- },
11
- "repository": {
12
- "type": "git",
13
- "url": "https://github.com/nicobailon/pi-mcp-adapter"
14
- },
15
5
  "keywords": [
16
6
  "pi-package",
17
7
  "pi",
@@ -23,16 +13,15 @@
23
13
  "claude",
24
14
  "llm"
25
15
  ],
26
- "scripts": {
27
- "test": "vitest run",
28
- "test:watch": "vitest",
29
- "test:coverage": "vitest run --coverage"
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/nicobailon/pi-mcp-adapter"
30
19
  },
31
- "pi": {
32
- "extensions": [
33
- "./index.ts"
34
- ],
35
- "video": "https://github.com/nicobailon/pi-mcp-adapter/raw/refs/heads/main/pi-mcp.mp4"
20
+ "license": "MIT",
21
+ "author": "Nico Bailon",
22
+ "type": "module",
23
+ "bin": {
24
+ "pi-mcp-adapter": "./cli.js"
36
25
  },
37
26
  "files": [
38
27
  "cli.js",
@@ -68,18 +57,29 @@
68
57
  "CHANGELOG.md",
69
58
  "LICENSE"
70
59
  ],
60
+ "scripts": {
61
+ "test": "vitest run",
62
+ "test:coverage": "vitest run --coverage",
63
+ "test:watch": "vitest"
64
+ },
71
65
  "dependencies": {
72
66
  "@modelcontextprotocol/ext-apps": "^1.2.2",
73
67
  "@modelcontextprotocol/sdk": "^1.25.1",
74
68
  "@sinclair/typebox": "^0.32.0",
75
69
  "zod": "^3.25.0 || ^4.0.0"
76
70
  },
77
- "peerDependencies": {
78
- "zod": "^3.25.0 || ^4.0.0"
79
- },
80
71
  "devDependencies": {
81
72
  "@types/node": "^20.0.0",
82
73
  "typescript": "^5.0.0",
83
74
  "vitest": "^3.0.0"
75
+ },
76
+ "peerDependencies": {
77
+ "zod": "^3.25.0 || ^4.0.0"
78
+ },
79
+ "pi": {
80
+ "extensions": [
81
+ "./index.ts"
82
+ ],
83
+ "video": "https://github.com/nicobailon/pi-mcp-adapter/raw/refs/heads/main/pi-mcp.mp4"
84
84
  }
85
85
  }
@@ -511,7 +511,7 @@ function buildPlanPromptOverlay(state: IPlanSessionState): string {
511
511
  const task = state.task?.trim() || "(unknown task)";
512
512
  const model = state.planModel ? `Planning model: ${state.planModel}` : "Planning model: current session model";
513
513
  const artifact = state.artifactPath ? `Artifact path: ${state.artifactPath}` : "Artifact path: unavailable";
514
- const modeLabel = state.mode === "architect" ? "Docyrus `/architect` planning mode." : "Docyrus `/plan` mode.";
514
+ const modeLabel = "Docyrus `/plan` mode.";
515
515
  return [
516
516
  modeLabel,
517
517
  PLAN_MODE_SYSTEM_PROMPT,
@@ -735,7 +735,115 @@ export function isPotentiallyMutatingShellCommand(command: string): boolean {
735
735
  }
736
736
 
737
737
  function buildBlockedToolReason(detail: string): string {
738
- return `A planning session is active. ${detail} Exit with /end-plan or /end-architect before implementing.`;
738
+ return `A planning session is active. ${detail} Exit with /end-plan before implementing.`;
739
+ }
740
+
741
+ const READONLY_STATE_TYPE = "readonly-session";
742
+ const READONLY_WIDGET_KEY = "readonly-mode";
743
+
744
+ interface IReadOnlySessionState {
745
+ active: boolean;
746
+ startedAt?: string;
747
+ }
748
+
749
+ let currentReadOnlyState: IReadOnlySessionState | undefined;
750
+
751
+ const READONLY_MODE_SYSTEM_PROMPT = [
752
+ "You are in Docyrus `/read-only` mode.",
753
+ "",
754
+ "You MUST NOT modify any files or remote state.",
755
+ "You can read files, search code, analyze, summarize, and answer questions.",
756
+ "Write and edit tools are disabled.",
757
+ "",
758
+ "Exit read-only mode with /end-read-only to resume normal operation.",
759
+ ].join("\n");
760
+
761
+ function getReadOnlyState(ctx: ExtensionContext): IReadOnlySessionState | undefined {
762
+ let state: IReadOnlySessionState | undefined;
763
+ for (const entry of ctx.sessionManager.getBranch()) {
764
+ if (entry.type === "custom" && entry.customType === READONLY_STATE_TYPE) {
765
+ state = entry.data as IReadOnlySessionState | undefined;
766
+ }
767
+ }
768
+
769
+ if (state?.active) {
770
+ return state;
771
+ }
772
+
773
+ return undefined;
774
+ }
775
+
776
+ function setReadOnlyWidget(ctx: ExtensionContext, state: IReadOnlySessionState | undefined): void {
777
+ if (!ctx.hasUI) {
778
+ return;
779
+ }
780
+
781
+ if (!state?.active) {
782
+ ctx.ui.setWidget(READONLY_WIDGET_KEY, undefined);
783
+ return;
784
+ }
785
+
786
+ ctx.ui.setWidget(READONLY_WIDGET_KEY, (_tui, theme) => {
787
+ const lines = [
788
+ theme.fg("accent", theme.bold("Read-only mode active")),
789
+ theme.fg("muted", "Write & edit tools disabled"),
790
+ theme.fg("warning", "Exit with /end-read-only"),
791
+ ];
792
+
793
+ const container = new Container();
794
+ container.addChild(new DynamicBorder((value: string) => theme.fg("accent", value)));
795
+ container.addChild(new Text(lines.join("\n"), 0, 0));
796
+ return container;
797
+ });
798
+ }
799
+
800
+ function buildBlockedReadOnlyReason(detail: string): string {
801
+ return `Read-only mode is active. ${detail} Exit with /end-read-only to resume editing.`;
802
+ }
803
+
804
+ function handleToolCallDuringReadOnly(event: ToolCallEvent, ctx: ExtensionContext): { block: boolean; reason: string } | undefined {
805
+ if (!getReadOnlyState(ctx)?.active) {
806
+ return undefined;
807
+ }
808
+
809
+ if (event.toolName === "edit" || event.toolName === "write") {
810
+ return {
811
+ block: true,
812
+ reason: buildBlockedReadOnlyReason(`The ${event.toolName} tool is disabled in read-only mode.`),
813
+ };
814
+ }
815
+
816
+ if (event.toolName === "bash" && isRecord(event.input) && typeof event.input.command === "string") {
817
+ if (isPotentiallyMutatingShellCommand(event.input.command)) {
818
+ return {
819
+ block: true,
820
+ reason: buildBlockedReadOnlyReason(`Mutating bash commands are disabled in read-only mode (${event.input.command}).`),
821
+ };
822
+ }
823
+ }
824
+
825
+ if (event.toolName === "todo" && isRecord(event.input) && typeof event.input.action === "string") {
826
+ if (shouldBlockTodoAction(event.input.action)) {
827
+ return {
828
+ block: true,
829
+ reason: buildBlockedReadOnlyReason(`Todo action "${event.input.action}" is disabled in read-only mode.`),
830
+ };
831
+ }
832
+ }
833
+
834
+ return undefined;
835
+ }
836
+
837
+ function handleUserBashDuringReadOnly(event: UserBashEvent, ctx: ExtensionContext) {
838
+ if (!getReadOnlyState(ctx)?.active) {
839
+ return undefined;
840
+ }
841
+
842
+ if (!isPotentiallyMutatingShellCommand(event.command)) {
843
+ return undefined;
844
+ }
845
+
846
+ return buildBlockedUserBashResult(`Mutating shell commands are disabled in read-only mode (${event.command}).`);
739
847
  }
740
848
 
741
849
  function buildBlockedUserBashResult(detail: string) {
@@ -900,7 +1008,11 @@ export async function startPlanningWorkflow(params: IPlanningStartParams): Promi
900
1008
  }
901
1009
 
902
1010
  try {
903
- await ensureArtifactFile(artifactPath, params.task ?? mode);
1011
+ if (mode === "architect") {
1012
+ await fs.mkdir(artifactPath, { recursive: true });
1013
+ } else {
1014
+ await ensureArtifactFile(artifactPath, params.task ?? mode);
1015
+ }
904
1016
  } catch (error) {
905
1017
  if (ctx.hasUI) {
906
1018
  const message = error instanceof Error ? error.message : String(error);
@@ -968,32 +1080,13 @@ export async function startPlanningWorkflow(params: IPlanningStartParams): Promi
968
1080
 
969
1081
  if (ctx.hasUI) {
970
1082
  const profileHint = profileName ? ` (profile: ${profileName})` : "";
971
- const modeLabel = mode === "architect" ? "Architect mode" : "Plan mode";
972
- ctx.ui.notify(`${modeLabel} active${profileHint}. Writing updates to ${artifactPath}`, "info");
1083
+ ctx.ui.notify(`Plan mode active${profileHint}. Writing updates to ${artifactPath}`, "info");
973
1084
  }
974
1085
 
975
1086
  pi.sendUserMessage(initialPrompt);
976
1087
  return true;
977
1088
  }
978
1089
 
979
- async function planHandler(pi: ExtensionAPI, ctx: ExtensionCommandContext, args: string): Promise<void> {
980
- const task = parsePlanTask(args) ?? undefined;
981
- const artifactPath = createPlanArtifactPath({
982
- cwd: ctx.cwd,
983
- task: task ?? "plan",
984
- date: new Date(),
985
- });
986
-
987
- await startPlanningWorkflow({
988
- pi,
989
- ctx,
990
- mode: "plan",
991
- task,
992
- artifactPath,
993
- initialPrompt: buildInitialPlanPrompt(task),
994
- });
995
- }
996
-
997
1090
  export async function endPlanningWorkflow(pi: ExtensionAPI, ctx: ExtensionCommandContext): Promise<void> {
998
1091
  const state = getPlanState(ctx);
999
1092
  if (!state?.active || !state.originId) {
@@ -1208,13 +1301,6 @@ function handleUserBashDuringPlan(event: UserBashEvent, ctx: ExtensionContext) {
1208
1301
  }
1209
1302
 
1210
1303
  export default function planExtension(pi: ExtensionAPI) {
1211
- pi.registerCommand("plan", {
1212
- description: "Start a planning-only branch with an optional dedicated planning model",
1213
- handler: async(args, ctx) => {
1214
- await planHandler(pi, ctx, args);
1215
- },
1216
- });
1217
-
1218
1304
  pi.registerCommand("end-plan", {
1219
1305
  description: "Leave plan mode, summarize the plan branch, and return to the original branch",
1220
1306
  handler: async(_args, ctx) => {
@@ -1222,37 +1308,85 @@ export default function planExtension(pi: ExtensionAPI) {
1222
1308
  },
1223
1309
  });
1224
1310
 
1225
- pi.registerCommand("end-architect", {
1226
- description: "Leave architect mode, summarize the architect branch, and return to the original branch",
1311
+ pi.registerCommand("plan-policy", {
1312
+ description: "Show effective planning-model policy and the resolved planning model",
1227
1313
  handler: async(_args, ctx) => {
1228
- await endPlanningWorkflow(pi, ctx);
1314
+ await planPolicyHandler(pi, ctx);
1229
1315
  },
1230
1316
  });
1231
1317
 
1232
- pi.registerCommand("plan-policy", {
1233
- description: "Show effective planning-model policy and the resolved planning model",
1318
+ pi.registerCommand("read-only", {
1319
+ description: "Enter read-only mode — write and edit tools are disabled, analysis and search remain available",
1234
1320
  handler: async(_args, ctx) => {
1235
- await planPolicyHandler(pi, ctx);
1321
+ if (getReadOnlyState(ctx)?.active) {
1322
+ if (ctx.hasUI) {
1323
+ ctx.ui.notify("Read-only mode is already active. Exit with /end-read-only first.", "warning");
1324
+ }
1325
+ return;
1326
+ }
1327
+
1328
+ const state: IReadOnlySessionState = {
1329
+ active: true,
1330
+ startedAt: new Date().toISOString(),
1331
+ };
1332
+ pi.appendEntry(READONLY_STATE_TYPE, state);
1333
+ currentReadOnlyState = state;
1334
+ setReadOnlyWidget(ctx, state);
1335
+
1336
+ if (ctx.hasUI) {
1337
+ ctx.ui.notify("Read-only mode active. Write & edit tools disabled. Exit with /end-read-only.", "info");
1338
+ }
1339
+ },
1340
+ });
1341
+
1342
+ pi.registerCommand("end-read-only", {
1343
+ description: "Exit read-only mode and resume normal operation",
1344
+ handler: async(_args, ctx) => {
1345
+ if (!getReadOnlyState(ctx)?.active) {
1346
+ if (ctx.hasUI) {
1347
+ ctx.ui.notify("No active read-only session was found.", "info");
1348
+ }
1349
+ return;
1350
+ }
1351
+
1352
+ pi.appendEntry(READONLY_STATE_TYPE, { active: false });
1353
+ currentReadOnlyState = undefined;
1354
+ setReadOnlyWidget(ctx, undefined);
1355
+
1356
+ if (ctx.hasUI) {
1357
+ ctx.ui.notify("Read-only mode ended. Normal operation resumed.", "info");
1358
+ }
1236
1359
  },
1237
1360
  });
1238
1361
 
1239
1362
  pi.on("before_agent_start", (event, ctx) => {
1240
- const state = getPlanState(ctx);
1241
- if (!state?.active) {
1363
+ const planState = getPlanState(ctx);
1364
+ const readOnlyState = getReadOnlyState(ctx);
1365
+ const overlays: string[] = [];
1366
+
1367
+ if (planState?.active) {
1368
+ overlays.push(buildPlanPromptOverlay(planState));
1369
+ }
1370
+
1371
+ if (readOnlyState?.active) {
1372
+ overlays.push(READONLY_MODE_SYSTEM_PROMPT);
1373
+ }
1374
+
1375
+ if (overlays.length === 0) {
1242
1376
  return;
1243
1377
  }
1244
1378
 
1245
1379
  return {
1246
- systemPrompt: [event.systemPrompt, buildPlanPromptOverlay(state)].filter(Boolean).join("\n\n"),
1380
+ systemPrompt: [event.systemPrompt, ...overlays].filter(Boolean).join("\n\n"),
1247
1381
  };
1248
1382
  });
1249
1383
 
1250
1384
  pi.on("tool_call", (event, ctx) => {
1251
- return handleToolCallDuringPlan(event, ctx);
1385
+ return handleToolCallDuringPlan(event, ctx) ?? handleToolCallDuringReadOnly(event, ctx);
1252
1386
  });
1253
1387
 
1254
1388
  pi.on("user_bash", (event, ctx) => {
1255
- return handleUserBashDuringPlan(event, ctx);
1389
+ return handleUserBashDuringPlan(event, ctx) ?? handleUserBashDuringReadOnly(event, ctx);
1256
1390
  });
1257
1391
 
1258
1392
  pi.on("agent_end", async(event, ctx) => {
@@ -1275,18 +1409,22 @@ export default function planExtension(pi: ExtensionAPI) {
1275
1409
 
1276
1410
  pi.on("session_start", async(_event, ctx) => {
1277
1411
  await syncPlanState(pi, ctx);
1278
- });
1279
-
1280
- pi.on("session_switch", async(_event, ctx) => {
1281
- await syncPlanState(pi, ctx);
1412
+ const readOnlyState = getReadOnlyState(ctx);
1413
+ currentReadOnlyState = readOnlyState;
1414
+ setReadOnlyWidget(ctx, readOnlyState);
1282
1415
  });
1283
1416
 
1284
1417
  pi.on("session_tree", async(_event, ctx) => {
1285
1418
  await syncPlanState(pi, ctx);
1419
+ const readOnlyState = getReadOnlyState(ctx);
1420
+ currentReadOnlyState = readOnlyState;
1421
+ setReadOnlyWidget(ctx, readOnlyState);
1286
1422
  });
1287
1423
 
1288
1424
  pi.on("session_shutdown", async(_event, ctx) => {
1289
1425
  currentPlanState = undefined;
1290
1426
  setPlanWidget(ctx, undefined);
1427
+ currentReadOnlyState = undefined;
1428
+ setReadOnlyWidget(ctx, undefined);
1291
1429
  });
1292
1430
  }
@@ -1267,24 +1267,6 @@ export default function(pi: ExtensionAPI) {
1267
1267
  applyEditor(pi, ctx);
1268
1268
  });
1269
1269
 
1270
- pi.on("session_switch", async(_event, ctx) => {
1271
- lastObservedModel = { provider: ctx.model?.provider, modelId: ctx.model?.id };
1272
- await ensureRuntime(pi, ctx);
1273
- customOverlay = null;
1274
-
1275
- const inferred = inferModeFromSelection(ctx, pi, runtime.data);
1276
- if (inferred) {
1277
- runtime.currentMode = inferred;
1278
- runtime.lastRealMode = inferred;
1279
- } else {
1280
- runtime.currentMode = CUSTOM_MODE_NAME;
1281
- customOverlay = getCurrentSelectionSpec(pi, ctx);
1282
- }
1283
-
1284
- applyEditor(pi, ctx);
1285
- });
1286
-
1287
-
1288
1270
  pi.on("model_select", async(event: ModelSelectEvent, ctx) => {
1289
1271
  // Always track the last observed model for overlay/store correctness.
1290
1272
  lastObservedModel = { provider: event.model.provider, modelId: event.model.id };
@@ -108,10 +108,6 @@ export default function promptUrlWidgetExtension(pi: ExtensionAPI) {
108
108
  });
109
109
  });
110
110
 
111
- pi.on("session_switch", async(_event, ctx) => {
112
- rebuildFromSession(ctx);
113
- });
114
-
115
111
  const getUserText = (content: string | { type: string; text?: string }[] | undefined): string => {
116
112
  if (!content) {return "";}
117
113
  if (typeof content === "string") {return content;}
@@ -938,10 +938,6 @@ export default function reviewExtension(pi: ExtensionAPI) {
938
938
  applyAllReviewState(ctx);
939
939
  });
940
940
 
941
- pi.on("session_switch", (_event, ctx) => {
942
- applyAllReviewState(ctx);
943
- });
944
-
945
941
  pi.on("session_tree", (_event, ctx) => {
946
942
  applyAllReviewState(ctx);
947
943
  });
@@ -9,6 +9,7 @@ Core behavior:
9
9
  - Start by inspecting real tenant state before making claims about apps, data sources, users, environments, auth state, or API shape.
10
10
  - If the repository contains `docyrus/knowledge/`, use `docyrus knowledge search`, `docyrus knowledge section`, and `docyrus knowledge expand` before broader repo exploration.
11
11
  - When `docyrus/knowledge/` exists, keep it updated and finish local work with `docyrus knowledge check`.
12
+ - If the repository contains `docyrus/project-plan/project-plan.json`, use `docyrus project-plan show` to understand current work scope and `docyrus project-plan get-task` to inspect individual tasks before starting work. Update task status with `docyrus project-plan set-task-status` as work progresses.
12
13
  - Use the installed Docyrus skills as your command and workflow reference.
13
14
  - Be careful with tenant-scoped mutations. Confirm real identifiers and current context before changing state.
14
15
  - Treat auth files, tokens, and `.docyrus` contents as sensitive.
@@ -24,4 +25,15 @@ Docyrus concepts you should understand and use accurately:
24
25
  - discover commands and tenant OpenAPI inspection
25
26
  - raw API access via the CLI when needed
26
27
 
28
+ Project plan system:
29
+
30
+ The project-plan is a repo-tracked work graph stored at `docyrus/project-plan/project-plan.json`. It organizes work into sections (anchored to knowledge graph section IDs), features, and tasks. A derived `PROJECT_PLAN.md` is always kept in sync.
31
+
32
+ - `docyrus project-plan show` — view the full hierarchy with statuses
33
+ - `docyrus project-plan get-task --taskId <id>` — inspect a task and its linked local subtasks
34
+ - `docyrus project-plan set-task-status --taskId <id> --status <status>` — advance task status (`planned` → `in_progress` → `done`, or `blocked`)
35
+ - `docyrus project-plan check` — validate that all section references and task fields are consistent
36
+
37
+ When working on a repo that has a project plan, read it at the start of a session to understand scope and priorities. Update task status as work progresses and after it completes.
38
+
27
39
  You are not a generic shell assistant first. You are a Docyrus-first operator that happens to have local tools.
@@ -20,6 +20,7 @@ Core behavior:
20
20
  - Use the local `docyrus` CLI for Docyrus platform state, tenant context, schema inspection, app and data-source operations, API discovery, and data verification.
21
21
  - If the repository contains `docyrus/knowledge/`, use `docyrus knowledge search`, `docyrus knowledge section`, and `docyrus knowledge expand` before coding so you start from documented repo intent rather than only source inspection.
22
22
  - When `docyrus/knowledge/` exists, keep it in sync with behavior changes and finish with `docyrus knowledge check`.
23
+ - If the repository contains `docyrus/project-plan/project-plan.json`, read it at session start with `docyrus project-plan show` to understand current priorities and work scope. Update task status with `docyrus project-plan set-task-status` as work begins and finishes. Use `docyrus project-plan create-linked-todo` to break agent-assigned tasks into local subtasks when needed.
23
24
  - Prefer `--json` whenever command output needs to be parsed, compared, or fed back into reasoning.
24
25
  - Start from real state before making claims about apps, data sources, users, fields, enums, environments, auth state, API shape, or deployment context.
25
26
  - Distinguish clearly between local code changes and remote Docyrus platform mutations.
@@ -69,6 +70,23 @@ Schema-first workflow for new Docyrus-backed apps and major features:
69
70
  - After schema changes, verify the resulting metadata and runtime behavior with `docyrus ds get`, `docyrus ds list`, and `docyrus discover`.
70
71
  - When the project uses generated collections or codegen from the OpenAPI spec, resync or regenerate those artifacts after schema changes if the repo workflow requires it.
71
72
 
73
+ Project plan system:
74
+
75
+ The project-plan is a repo-tracked work graph stored at `docyrus/project-plan/project-plan.json` with a derived `PROJECT_PLAN.md` always kept in sync. Work is organized into sections (anchored to knowledge graph section IDs), features, and tasks. Tasks have a type (`new-implementation`, `bug-fix`, `api-test`, `browser-automation-test`, `work`), an assignee (`agent` or `user`), a status (`planned`, `in_progress`, `blocked`, `done`), and optional acceptance criteria.
76
+
77
+ Key commands:
78
+
79
+ - `docyrus project-plan show` — view the full hierarchy with feature and task statuses
80
+ - `docyrus project-plan get-task --taskId <id>` — inspect a specific task and its linked local subtasks
81
+ - `docyrus project-plan set-task-status --taskId <id> --status <status>` — advance task status
82
+ - `docyrus project-plan create-linked-todo --taskId <id> --title <title> --body <body>` — create a local `.pi/todos` subtask linked to an agent-assigned canonical task
83
+ - `docyrus project-plan upsert-feature --sectionId <id> --title <title> --slug <slug>` — create or update a feature
84
+ - `docyrus project-plan upsert-task --featureId <id> --title <title> --type <type> --assignee <assignee>` — create or update a task
85
+ - `docyrus project-plan check` — validate section references and graph integrity
86
+ - `docyrus project-plan ensure` — initialize the graph from knowledge sections if it does not yet exist
87
+
88
+ Workflow: read the project plan at session start → set relevant tasks to `in_progress` before beginning → create linked todos for complex implementation tasks → set tasks to `done` after work is verified.
89
+
72
90
  Docyrus CLI workflows you should rely on:
73
91
 
74
92
  - Use `docyrus auth who`, `docyrus auth accounts ...`, `docyrus auth tenants ...`, and `docyrus env ...` to confirm active identity, tenant, and environment.