@ilya-lesikov/pi-pi 0.1.0
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/3p/pi-ask-user/index.ts +1835 -0
- package/3p/pi-ask-user/package.json +50 -0
- package/3p/pi-ask-user/single-select-layout.ts +203 -0
- package/3p/pi-lsp/extensions/lsp/client.ts +542 -0
- package/3p/pi-lsp/extensions/lsp/config.ts +135 -0
- package/3p/pi-lsp/extensions/lsp/effects/command.ts +42 -0
- package/3p/pi-lsp/extensions/lsp/effects/filesystem.ts +50 -0
- package/3p/pi-lsp/extensions/lsp/effects/runtime.ts +21 -0
- package/3p/pi-lsp/extensions/lsp/errors.ts +113 -0
- package/3p/pi-lsp/extensions/lsp/formatting.ts +315 -0
- package/3p/pi-lsp/extensions/lsp/index.ts +231 -0
- package/3p/pi-lsp/extensions/lsp/protocol.ts +209 -0
- package/3p/pi-lsp/extensions/lsp/retry.ts +39 -0
- package/3p/pi-lsp/extensions/lsp/tools/programs.ts +293 -0
- package/3p/pi-lsp/extensions/lsp/tools.ts +89 -0
- package/3p/pi-lsp/extensions/lsp/types.ts +243 -0
- package/3p/pi-lsp/package.json +54 -0
- package/3p/pi-plannotator/AGENTS.md +583 -0
- package/3p/pi-plannotator/apps/pi-extension/README.md +228 -0
- package/3p/pi-plannotator/apps/pi-extension/assistant-message.ts +128 -0
- package/3p/pi-plannotator/apps/pi-extension/config.test.ts +166 -0
- package/3p/pi-plannotator/apps/pi-extension/config.ts +318 -0
- package/3p/pi-plannotator/apps/pi-extension/current-pi-session.ts +147 -0
- package/3p/pi-plannotator/apps/pi-extension/index.ts +1279 -0
- package/3p/pi-plannotator/apps/pi-extension/package.json +56 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.test.ts +13 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-browser.ts +616 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator-events.ts +342 -0
- package/3p/pi-plannotator/apps/pi-extension/plannotator.json +12 -0
- package/3p/pi-plannotator/apps/pi-extension/server/agent-jobs.ts +515 -0
- package/3p/pi-plannotator/apps/pi-extension/server/ai-runtime.ts +169 -0
- package/3p/pi-plannotator/apps/pi-extension/server/annotations.ts +85 -0
- package/3p/pi-plannotator/apps/pi-extension/server/external-annotations.ts +189 -0
- package/3p/pi-plannotator/apps/pi-extension/server/handlers.ts +210 -0
- package/3p/pi-plannotator/apps/pi-extension/server/helpers.ts +78 -0
- package/3p/pi-plannotator/apps/pi-extension/server/ide.ts +46 -0
- package/3p/pi-plannotator/apps/pi-extension/server/integrations.ts +195 -0
- package/3p/pi-plannotator/apps/pi-extension/server/network.test.ts +158 -0
- package/3p/pi-plannotator/apps/pi-extension/server/network.ts +268 -0
- package/3p/pi-plannotator/apps/pi-extension/server/pr.ts +126 -0
- package/3p/pi-plannotator/apps/pi-extension/server/project.ts +64 -0
- package/3p/pi-plannotator/apps/pi-extension/server/reference.ts +362 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverAnnotate.ts +199 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverPlan.ts +505 -0
- package/3p/pi-plannotator/apps/pi-extension/server/serverReview.ts +1229 -0
- package/3p/pi-plannotator/apps/pi-extension/server/vcs.ts +121 -0
- package/3p/pi-plannotator/apps/pi-extension/server.test.ts +966 -0
- package/3p/pi-plannotator/apps/pi-extension/server.ts +44 -0
- package/3p/pi-plannotator/apps/pi-extension/tool-scope.test.ts +88 -0
- package/3p/pi-plannotator/apps/pi-extension/tool-scope.ts +39 -0
- package/3p/pi-plannotator/apps/pi-extension/tsconfig.json +16 -0
- package/3p/pi-plannotator/apps/pi-extension/vendor.sh +48 -0
- package/3p/pi-plannotator/package.json +53 -0
- package/3p/pi-plannotator/packages/ai/ai.test.ts +1407 -0
- package/3p/pi-plannotator/packages/ai/base-session.ts +94 -0
- package/3p/pi-plannotator/packages/ai/context.ts +250 -0
- package/3p/pi-plannotator/packages/ai/endpoints.ts +326 -0
- package/3p/pi-plannotator/packages/ai/index.ts +105 -0
- package/3p/pi-plannotator/packages/ai/package.json +21 -0
- package/3p/pi-plannotator/packages/ai/provider.ts +103 -0
- package/3p/pi-plannotator/packages/ai/providers/claude-agent-sdk.ts +447 -0
- package/3p/pi-plannotator/packages/ai/providers/codex-sdk.ts +430 -0
- package/3p/pi-plannotator/packages/ai/providers/command-path.ts +115 -0
- package/3p/pi-plannotator/packages/ai/providers/opencode-sdk.ts +546 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-events.ts +110 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-sdk-node.ts +425 -0
- package/3p/pi-plannotator/packages/ai/providers/pi-sdk.ts +469 -0
- package/3p/pi-plannotator/packages/ai/session-manager.ts +195 -0
- package/3p/pi-plannotator/packages/ai/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/ai/types.ts +379 -0
- package/3p/pi-plannotator/packages/server/agent-jobs.ts +538 -0
- package/3p/pi-plannotator/packages/server/agent-review-message.test.ts +135 -0
- package/3p/pi-plannotator/packages/server/agent-review-message.ts +243 -0
- package/3p/pi-plannotator/packages/server/ai-runtime.ts +108 -0
- package/3p/pi-plannotator/packages/server/annotate.ts +401 -0
- package/3p/pi-plannotator/packages/server/browser.test.ts +87 -0
- package/3p/pi-plannotator/packages/server/browser.ts +245 -0
- package/3p/pi-plannotator/packages/server/claude-review.ts +352 -0
- package/3p/pi-plannotator/packages/server/code-nav.ts +73 -0
- package/3p/pi-plannotator/packages/server/codex-review-schema.json +41 -0
- package/3p/pi-plannotator/packages/server/codex-review.ts +330 -0
- package/3p/pi-plannotator/packages/server/config.ts +8 -0
- package/3p/pi-plannotator/packages/server/draft.ts +1 -0
- package/3p/pi-plannotator/packages/server/editor-annotations.ts +76 -0
- package/3p/pi-plannotator/packages/server/external-annotations.test.ts +18 -0
- package/3p/pi-plannotator/packages/server/external-annotations.ts +207 -0
- package/3p/pi-plannotator/packages/server/git.ts +141 -0
- package/3p/pi-plannotator/packages/server/goal-setup.test.ts +55 -0
- package/3p/pi-plannotator/packages/server/goal-setup.ts +248 -0
- package/3p/pi-plannotator/packages/server/ide.ts +43 -0
- package/3p/pi-plannotator/packages/server/image.test.ts +63 -0
- package/3p/pi-plannotator/packages/server/image.ts +66 -0
- package/3p/pi-plannotator/packages/server/index.ts +648 -0
- package/3p/pi-plannotator/packages/server/integrations.test.ts +172 -0
- package/3p/pi-plannotator/packages/server/integrations.ts +214 -0
- package/3p/pi-plannotator/packages/server/jj.test.ts +69 -0
- package/3p/pi-plannotator/packages/server/jj.ts +86 -0
- package/3p/pi-plannotator/packages/server/p4.ts +417 -0
- package/3p/pi-plannotator/packages/server/package.json +40 -0
- package/3p/pi-plannotator/packages/server/path-utils.ts +18 -0
- package/3p/pi-plannotator/packages/server/pr.ts +146 -0
- package/3p/pi-plannotator/packages/server/project.test.ts +115 -0
- package/3p/pi-plannotator/packages/server/project.ts +45 -0
- package/3p/pi-plannotator/packages/server/reference-handlers.ts +404 -0
- package/3p/pi-plannotator/packages/server/remote.test.ts +150 -0
- package/3p/pi-plannotator/packages/server/remote.ts +74 -0
- package/3p/pi-plannotator/packages/server/repo.ts +80 -0
- package/3p/pi-plannotator/packages/server/resolve-file.test.ts +310 -0
- package/3p/pi-plannotator/packages/server/review-workspace.test.ts +1032 -0
- package/3p/pi-plannotator/packages/server/review-workspace.ts +48 -0
- package/3p/pi-plannotator/packages/server/review.ts +1318 -0
- package/3p/pi-plannotator/packages/server/sessions.ts +111 -0
- package/3p/pi-plannotator/packages/server/share-url.ts +53 -0
- package/3p/pi-plannotator/packages/server/shared-handlers.test.ts +43 -0
- package/3p/pi-plannotator/packages/server/shared-handlers.ts +177 -0
- package/3p/pi-plannotator/packages/server/storage.test.ts +176 -0
- package/3p/pi-plannotator/packages/server/storage.ts +17 -0
- package/3p/pi-plannotator/packages/server/tour/tour-review.test.ts +146 -0
- package/3p/pi-plannotator/packages/server/tour/tour-review.ts +604 -0
- package/3p/pi-plannotator/packages/server/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/server/vcs.test.ts +48 -0
- package/3p/pi-plannotator/packages/server/vcs.ts +80 -0
- package/3p/pi-plannotator/packages/shared/agent-jobs.ts +132 -0
- package/3p/pi-plannotator/packages/shared/agents.ts +53 -0
- package/3p/pi-plannotator/packages/shared/annotate-args.test.ts +386 -0
- package/3p/pi-plannotator/packages/shared/annotate-args.ts +107 -0
- package/3p/pi-plannotator/packages/shared/at-reference.test.ts +99 -0
- package/3p/pi-plannotator/packages/shared/at-reference.ts +52 -0
- package/3p/pi-plannotator/packages/shared/checklist.ts +52 -0
- package/3p/pi-plannotator/packages/shared/code-file.test.ts +112 -0
- package/3p/pi-plannotator/packages/shared/code-file.ts +41 -0
- package/3p/pi-plannotator/packages/shared/code-nav.test.ts +515 -0
- package/3p/pi-plannotator/packages/shared/code-nav.ts +436 -0
- package/3p/pi-plannotator/packages/shared/compress.ts +51 -0
- package/3p/pi-plannotator/packages/shared/config.ts +262 -0
- package/3p/pi-plannotator/packages/shared/crypto.test.ts +172 -0
- package/3p/pi-plannotator/packages/shared/crypto.ts +97 -0
- package/3p/pi-plannotator/packages/shared/data-dir.ts +42 -0
- package/3p/pi-plannotator/packages/shared/diff-paths.test.ts +30 -0
- package/3p/pi-plannotator/packages/shared/diff-paths.ts +137 -0
- package/3p/pi-plannotator/packages/shared/draft.ts +64 -0
- package/3p/pi-plannotator/packages/shared/external-annotation.ts +397 -0
- package/3p/pi-plannotator/packages/shared/extract-code-paths.test.ts +59 -0
- package/3p/pi-plannotator/packages/shared/extract-code-paths.ts +66 -0
- package/3p/pi-plannotator/packages/shared/favicon.ts +5 -0
- package/3p/pi-plannotator/packages/shared/feedback-templates.test.ts +65 -0
- package/3p/pi-plannotator/packages/shared/feedback-templates.ts +29 -0
- package/3p/pi-plannotator/packages/shared/goal-setup.test.ts +231 -0
- package/3p/pi-plannotator/packages/shared/goal-setup.ts +336 -0
- package/3p/pi-plannotator/packages/shared/html-to-markdown.test.ts +62 -0
- package/3p/pi-plannotator/packages/shared/html-to-markdown.ts +32 -0
- package/3p/pi-plannotator/packages/shared/improvement-hooks.test.ts +135 -0
- package/3p/pi-plannotator/packages/shared/improvement-hooks.ts +115 -0
- package/3p/pi-plannotator/packages/shared/integrations-common.ts +243 -0
- package/3p/pi-plannotator/packages/shared/jj-core.test.ts +236 -0
- package/3p/pi-plannotator/packages/shared/jj-core.ts +433 -0
- package/3p/pi-plannotator/packages/shared/package.json +55 -0
- package/3p/pi-plannotator/packages/shared/pfm-reminder.test.ts +88 -0
- package/3p/pi-plannotator/packages/shared/pfm-reminder.ts +80 -0
- package/3p/pi-plannotator/packages/shared/pr-github.ts +661 -0
- package/3p/pi-plannotator/packages/shared/pr-gitlab.test.ts +202 -0
- package/3p/pi-plannotator/packages/shared/pr-gitlab.ts +620 -0
- package/3p/pi-plannotator/packages/shared/pr-provider.test.ts +266 -0
- package/3p/pi-plannotator/packages/shared/pr-provider.ts +123 -0
- package/3p/pi-plannotator/packages/shared/pr-stack.test.ts +104 -0
- package/3p/pi-plannotator/packages/shared/pr-stack.ts +194 -0
- package/3p/pi-plannotator/packages/shared/pr-types.ts +326 -0
- package/3p/pi-plannotator/packages/shared/project.ts +71 -0
- package/3p/pi-plannotator/packages/shared/prompts-integration.test.ts +421 -0
- package/3p/pi-plannotator/packages/shared/prompts.test.ts +504 -0
- package/3p/pi-plannotator/packages/shared/prompts.ts +247 -0
- package/3p/pi-plannotator/packages/shared/reference-common.ts +87 -0
- package/3p/pi-plannotator/packages/shared/repo.ts +71 -0
- package/3p/pi-plannotator/packages/shared/resolve-file.test.ts +113 -0
- package/3p/pi-plannotator/packages/shared/resolve-file.ts +509 -0
- package/3p/pi-plannotator/packages/shared/review-args.test.ts +64 -0
- package/3p/pi-plannotator/packages/shared/review-args.ts +85 -0
- package/3p/pi-plannotator/packages/shared/review-core.test.ts +286 -0
- package/3p/pi-plannotator/packages/shared/review-core.ts +895 -0
- package/3p/pi-plannotator/packages/shared/review-workspace-node.ts +230 -0
- package/3p/pi-plannotator/packages/shared/review-workspace.ts +436 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff-types.ts +76 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff.test.ts +322 -0
- package/3p/pi-plannotator/packages/shared/semantic-diff.ts +520 -0
- package/3p/pi-plannotator/packages/shared/storage.ts +378 -0
- package/3p/pi-plannotator/packages/shared/tour.ts +61 -0
- package/3p/pi-plannotator/packages/shared/tsconfig.json +15 -0
- package/3p/pi-plannotator/packages/shared/types.ts +29 -0
- package/3p/pi-plannotator/packages/shared/url-to-markdown.test.ts +177 -0
- package/3p/pi-plannotator/packages/shared/url-to-markdown.ts +351 -0
- package/3p/pi-plannotator/packages/shared/vcs-core.test.ts +332 -0
- package/3p/pi-plannotator/packages/shared/vcs-core.ts +482 -0
- package/3p/pi-plannotator/packages/shared/worktree-pool.test.ts +162 -0
- package/3p/pi-plannotator/packages/shared/worktree-pool.ts +103 -0
- package/3p/pi-plannotator/packages/shared/worktree.ts +119 -0
- package/3p/pi-subagents/package.json +50 -0
- package/3p/pi-subagents/src/agent-manager.ts +413 -0
- package/3p/pi-subagents/src/agent-runner.ts +502 -0
- package/3p/pi-subagents/src/agent-types.ts +248 -0
- package/3p/pi-subagents/src/context.ts +58 -0
- package/3p/pi-subagents/src/cross-extension-rpc.ts +135 -0
- package/3p/pi-subagents/src/custom-agents.ts +137 -0
- package/3p/pi-subagents/src/default-agents.ts +144 -0
- package/3p/pi-subagents/src/env.ts +33 -0
- package/3p/pi-subagents/src/group-join.ts +141 -0
- package/3p/pi-subagents/src/index.ts +1811 -0
- package/3p/pi-subagents/src/invocation-config.ts +40 -0
- package/3p/pi-subagents/src/memory.ts +165 -0
- package/3p/pi-subagents/src/model-resolver.ts +81 -0
- package/3p/pi-subagents/src/output-file.ts +77 -0
- package/3p/pi-subagents/src/prompts.ts +85 -0
- package/3p/pi-subagents/src/skill-loader.ts +79 -0
- package/3p/pi-subagents/src/types.ts +111 -0
- package/3p/pi-subagents/src/ui/agent-widget.ts +496 -0
- package/3p/pi-subagents/src/ui/conversation-viewer.ts +250 -0
- package/3p/pi-subagents/src/worktree.ts +162 -0
- package/3p/pi-tasks/package.json +52 -0
- package/3p/pi-tasks/src/auto-clear.ts +91 -0
- package/3p/pi-tasks/src/index.ts +1150 -0
- package/3p/pi-tasks/src/process-tracker.ts +140 -0
- package/3p/pi-tasks/src/reminder-cadence.ts +90 -0
- package/3p/pi-tasks/src/task-store.ts +324 -0
- package/3p/pi-tasks/src/tasks-config.ts +27 -0
- package/3p/pi-tasks/src/types.ts +40 -0
- package/3p/pi-tasks/src/ui/settings-menu.ts +152 -0
- package/3p/pi-tasks/src/ui/task-widget.ts +296 -0
- package/AGENTS.md +28 -0
- package/LICENSE +201 -0
- package/extensions/orchestrator/agents/brainstorm-reviewer.ts +77 -0
- package/extensions/orchestrator/agents/code-reviewer.ts +89 -0
- package/extensions/orchestrator/agents/explore.ts +33 -0
- package/extensions/orchestrator/agents/librarian.ts +43 -0
- package/extensions/orchestrator/agents/plan-reviewer.ts +75 -0
- package/extensions/orchestrator/agents/planner.ts +61 -0
- package/extensions/orchestrator/agents/registry.ts +156 -0
- package/extensions/orchestrator/agents/task.ts +50 -0
- package/extensions/orchestrator/agents/tool-routing.ts +84 -0
- package/extensions/orchestrator/ast-search.ts +85 -0
- package/extensions/orchestrator/cbm.ts +330 -0
- package/extensions/orchestrator/command-handlers.test.ts +163 -0
- package/extensions/orchestrator/command-handlers.ts +116 -0
- package/extensions/orchestrator/commands.test.ts +81 -0
- package/extensions/orchestrator/commands.ts +75 -0
- package/extensions/orchestrator/config.test.ts +221 -0
- package/extensions/orchestrator/config.ts +230 -0
- package/extensions/orchestrator/context.test.ts +293 -0
- package/extensions/orchestrator/context.ts +206 -0
- package/extensions/orchestrator/event-handlers.test.ts +190 -0
- package/extensions/orchestrator/event-handlers.ts +1416 -0
- package/extensions/orchestrator/exa.ts +104 -0
- package/extensions/orchestrator/flant-infra.ts +486 -0
- package/extensions/orchestrator/index.ts +80 -0
- package/extensions/orchestrator/integration.test.ts +1214 -0
- package/extensions/orchestrator/orchestrator.test.ts +252 -0
- package/extensions/orchestrator/orchestrator.ts +538 -0
- package/extensions/orchestrator/phases/brainstorm.test.ts +15 -0
- package/extensions/orchestrator/phases/brainstorm.ts +273 -0
- package/extensions/orchestrator/phases/implementation.ts +40 -0
- package/extensions/orchestrator/phases/machine.test.ts +293 -0
- package/extensions/orchestrator/phases/machine.ts +209 -0
- package/extensions/orchestrator/phases/planning.ts +255 -0
- package/extensions/orchestrator/phases/review.ts +193 -0
- package/extensions/orchestrator/plannotator.ts +56 -0
- package/extensions/orchestrator/pp-menu.ts +866 -0
- package/extensions/orchestrator/state.test.ts +343 -0
- package/extensions/orchestrator/state.ts +237 -0
- package/extensions/orchestrator/validate-artifacts.test.ts +88 -0
- package/extensions/orchestrator/validate-artifacts.ts +272 -0
- package/extensions/orchestrator/vendor.d.ts +26 -0
- package/package.json +73 -0
- package/scripts/postinstall.sh +18 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Annotate Server
|
|
3
|
+
*
|
|
4
|
+
* Provides a server for annotating arbitrary markdown files.
|
|
5
|
+
* Follows the same patterns as the review server but serves
|
|
6
|
+
* markdown content via /api/plan so the plan editor UI can
|
|
7
|
+
* render it without modifications.
|
|
8
|
+
*
|
|
9
|
+
* Environment variables:
|
|
10
|
+
* PLANNOTATOR_REMOTE - Set to "1"/"true" for remote, "0"/"false" for local
|
|
11
|
+
* PLANNOTATOR_PORT - Fixed port to use (default: random locally, 19432 for remote)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { isRemoteSession, getServerHostname, getServerPort } from "./remote";
|
|
15
|
+
import { getRepoInfo } from "./repo";
|
|
16
|
+
import type { Origin } from "@plannotator/shared/agents";
|
|
17
|
+
import { handleImage, handleUpload, handleServerReady, handleDraftSave, handleDraftLoad, handleDraftDelete, handleFavicon } from "./shared-handlers";
|
|
18
|
+
import { handleDoc, handleDocExists, handleFileBrowserFiles, handleObsidianVaults, handleObsidianFiles, handleObsidianDoc } from "./reference-handlers";
|
|
19
|
+
import { warmFileListCache } from "@plannotator/shared/resolve-file";
|
|
20
|
+
import { contentHash, deleteDraft } from "./draft";
|
|
21
|
+
import { createExternalAnnotationHandler } from "./external-annotations";
|
|
22
|
+
import { saveConfig, detectGitUser, getServerConfig } from "./config";
|
|
23
|
+
import { dirname, resolve as resolvePath } from "path";
|
|
24
|
+
import { isWSL } from "./browser";
|
|
25
|
+
import { AI_QUERY_ENDPOINT, createAIRuntime } from "./ai-runtime";
|
|
26
|
+
import type { AIEndpoints } from "@plannotator/ai";
|
|
27
|
+
|
|
28
|
+
// Re-export utilities
|
|
29
|
+
export { isRemoteSession, getServerPort } from "./remote";
|
|
30
|
+
export { openBrowser } from "./browser";
|
|
31
|
+
export { handleServerReady as handleAnnotateServerReady } from "./shared-handlers";
|
|
32
|
+
|
|
33
|
+
// --- Types ---
|
|
34
|
+
|
|
35
|
+
export interface AnnotateServerOptions {
|
|
36
|
+
/** Markdown content of the file to annotate */
|
|
37
|
+
markdown: string;
|
|
38
|
+
/** Original file path (for display purposes) */
|
|
39
|
+
filePath: string;
|
|
40
|
+
/** HTML content to serve for the UI */
|
|
41
|
+
htmlContent: string;
|
|
42
|
+
/** Origin identifier for UI customization */
|
|
43
|
+
origin?: Origin;
|
|
44
|
+
/** UI mode: "annotate" for files, "annotate-last" for last agent message, "annotate-folder" for folders */
|
|
45
|
+
mode?: "annotate" | "annotate-last" | "annotate-folder";
|
|
46
|
+
/** Folder path when annotating a directory (used as projectRoot for file browser) */
|
|
47
|
+
folderPath?: string;
|
|
48
|
+
/**
|
|
49
|
+
* Recent assistant messages for `annotate-last` mode (newest-first). When
|
|
50
|
+
* provided with more than one entry, the editor renders a picker so users
|
|
51
|
+
* can choose which message to annotate; index 0 is the default selection
|
|
52
|
+
* and matches the legacy "last message" behavior.
|
|
53
|
+
*/
|
|
54
|
+
recentMessages?: { messageId: string; text: string; timestamp?: string }[];
|
|
55
|
+
/** Whether URL sharing is enabled (default: true) */
|
|
56
|
+
sharingEnabled?: boolean;
|
|
57
|
+
/** Custom base URL for share links */
|
|
58
|
+
shareBaseUrl?: string;
|
|
59
|
+
/** Base URL of the paste service API for short URL sharing */
|
|
60
|
+
pasteApiUrl?: string;
|
|
61
|
+
/** Source attribution: original URL or filename (e.g. "https://..." or "index.html") */
|
|
62
|
+
sourceInfo?: string;
|
|
63
|
+
/** True when `markdown` was produced by Turndown/Jina (HTML or URL) —
|
|
64
|
+
* feedback line numbers won't match the original source. */
|
|
65
|
+
sourceConverted?: boolean;
|
|
66
|
+
/** Enable review-gate UX: adds an Approve button alongside Close/Send Annotations */
|
|
67
|
+
gate?: boolean;
|
|
68
|
+
/** Raw HTML content for direct iframe rendering (--render-html mode) */
|
|
69
|
+
rawHtml?: string;
|
|
70
|
+
/** Render HTML as-is in an iframe instead of converting to markdown */
|
|
71
|
+
renderHtml?: boolean;
|
|
72
|
+
/** Session-level force-markdown preference (`--markdown`). Exposed in /api/plan so the
|
|
73
|
+
* frontend appends `&convert=1` when navigating folder/linked HTML files. */
|
|
74
|
+
convertHtml?: boolean;
|
|
75
|
+
/** Called when server starts with the URL, remote status, and port */
|
|
76
|
+
onReady?: (url: string, isRemote: boolean, port: number) => void;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface AnnotateServerResult {
|
|
80
|
+
/** The port the server is running on */
|
|
81
|
+
port: number;
|
|
82
|
+
/** The full URL to access the server */
|
|
83
|
+
url: string;
|
|
84
|
+
/** Whether running in remote mode */
|
|
85
|
+
isRemote: boolean;
|
|
86
|
+
/** Wait for user feedback submission */
|
|
87
|
+
waitForDecision: () => Promise<{
|
|
88
|
+
feedback: string;
|
|
89
|
+
annotations: unknown[];
|
|
90
|
+
exit?: boolean;
|
|
91
|
+
approved?: boolean;
|
|
92
|
+
selectedMessageId?: string;
|
|
93
|
+
feedbackScope?: "message" | "messages";
|
|
94
|
+
}>;
|
|
95
|
+
/** Stop the server */
|
|
96
|
+
stop: () => void;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// --- Server Implementation ---
|
|
100
|
+
|
|
101
|
+
const MAX_RETRIES = 5;
|
|
102
|
+
const RETRY_DELAY_MS = 500;
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Start the Annotate server
|
|
106
|
+
*
|
|
107
|
+
* Handles:
|
|
108
|
+
* - Remote detection and port configuration
|
|
109
|
+
* - API routes (/api/plan with mode:"annotate", /api/feedback)
|
|
110
|
+
* - Port conflict retries
|
|
111
|
+
*/
|
|
112
|
+
export async function startAnnotateServer(
|
|
113
|
+
options: AnnotateServerOptions
|
|
114
|
+
): Promise<AnnotateServerResult> {
|
|
115
|
+
// Side-channel pre-warm so /api/doc/exists POSTs land on warm cache.
|
|
116
|
+
void warmFileListCache(process.cwd(), "code");
|
|
117
|
+
|
|
118
|
+
const {
|
|
119
|
+
markdown,
|
|
120
|
+
filePath,
|
|
121
|
+
htmlContent,
|
|
122
|
+
origin,
|
|
123
|
+
mode = "annotate",
|
|
124
|
+
folderPath,
|
|
125
|
+
recentMessages,
|
|
126
|
+
sourceInfo,
|
|
127
|
+
sourceConverted,
|
|
128
|
+
sharingEnabled = true,
|
|
129
|
+
shareBaseUrl,
|
|
130
|
+
pasteApiUrl,
|
|
131
|
+
gate = false,
|
|
132
|
+
rawHtml,
|
|
133
|
+
renderHtml = false,
|
|
134
|
+
convertHtml = false,
|
|
135
|
+
onReady,
|
|
136
|
+
} = options;
|
|
137
|
+
|
|
138
|
+
const isRemote = isRemoteSession();
|
|
139
|
+
const configuredPort = getServerPort();
|
|
140
|
+
const wslFlag = await isWSL();
|
|
141
|
+
const gitUser = detectGitUser();
|
|
142
|
+
const draftSource =
|
|
143
|
+
mode === "annotate-folder" && folderPath
|
|
144
|
+
? `folder:${resolvePath(folderPath)}`
|
|
145
|
+
: renderHtml && rawHtml ? rawHtml : markdown;
|
|
146
|
+
const draftKey = contentHash(draftSource);
|
|
147
|
+
const externalAnnotations = createExternalAnnotationHandler("plan");
|
|
148
|
+
const aiRuntime = await createAIRuntime();
|
|
149
|
+
|
|
150
|
+
// Detect repo info (cached for this session)
|
|
151
|
+
const repoInfo = await getRepoInfo();
|
|
152
|
+
|
|
153
|
+
// Decision promise
|
|
154
|
+
let resolveDecision: (result: {
|
|
155
|
+
feedback: string;
|
|
156
|
+
annotations: unknown[];
|
|
157
|
+
exit?: boolean;
|
|
158
|
+
approved?: boolean;
|
|
159
|
+
selectedMessageId?: string;
|
|
160
|
+
feedbackScope?: "message" | "messages";
|
|
161
|
+
}) => void;
|
|
162
|
+
const decisionPromise = new Promise<{
|
|
163
|
+
feedback: string;
|
|
164
|
+
annotations: unknown[];
|
|
165
|
+
exit?: boolean;
|
|
166
|
+
approved?: boolean;
|
|
167
|
+
selectedMessageId?: string;
|
|
168
|
+
feedbackScope?: "message" | "messages";
|
|
169
|
+
}>((resolve) => {
|
|
170
|
+
resolveDecision = resolve;
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// Start server with retry logic
|
|
174
|
+
let server: ReturnType<typeof Bun.serve> | null = null;
|
|
175
|
+
|
|
176
|
+
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
177
|
+
try {
|
|
178
|
+
server = Bun.serve({
|
|
179
|
+
hostname: getServerHostname(),
|
|
180
|
+
port: configuredPort,
|
|
181
|
+
|
|
182
|
+
async fetch(req, server) {
|
|
183
|
+
const url = new URL(req.url);
|
|
184
|
+
|
|
185
|
+
// API: Get plan content (reuse /api/plan so the plan editor UI works)
|
|
186
|
+
if (url.pathname === "/api/plan" && req.method === "GET") {
|
|
187
|
+
return Response.json({
|
|
188
|
+
plan: markdown,
|
|
189
|
+
origin,
|
|
190
|
+
mode,
|
|
191
|
+
filePath,
|
|
192
|
+
sourceInfo,
|
|
193
|
+
sourceConverted: sourceConverted ?? false,
|
|
194
|
+
gate,
|
|
195
|
+
renderAs: renderHtml && rawHtml ? 'html' as const : 'markdown' as const,
|
|
196
|
+
...(renderHtml && rawHtml ? { rawHtml } : {}),
|
|
197
|
+
convertHtml,
|
|
198
|
+
sharingEnabled,
|
|
199
|
+
shareBaseUrl,
|
|
200
|
+
pasteApiUrl,
|
|
201
|
+
repoInfo,
|
|
202
|
+
projectRoot: folderPath || process.cwd(),
|
|
203
|
+
isWSL: wslFlag,
|
|
204
|
+
serverConfig: getServerConfig(gitUser),
|
|
205
|
+
...(recentMessages ? { recentMessages } : {}),
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// API: Update user config (write-back to ~/.plannotator/config.json)
|
|
210
|
+
if (url.pathname === "/api/config" && req.method === "POST") {
|
|
211
|
+
try {
|
|
212
|
+
const body = (await req.json()) as { displayName?: string; diffOptions?: Record<string, unknown>; conventionalComments?: boolean; conventionalLabels?: unknown[] | null };
|
|
213
|
+
const toSave: Record<string, unknown> = {};
|
|
214
|
+
if (body.displayName !== undefined) toSave.displayName = body.displayName;
|
|
215
|
+
if (body.diffOptions !== undefined) toSave.diffOptions = body.diffOptions;
|
|
216
|
+
if (body.conventionalComments !== undefined) toSave.conventionalComments = body.conventionalComments;
|
|
217
|
+
if (body.conventionalLabels !== undefined) toSave.conventionalLabels = body.conventionalLabels;
|
|
218
|
+
if (Object.keys(toSave).length > 0) saveConfig(toSave as Parameters<typeof saveConfig>[0]);
|
|
219
|
+
return Response.json({ ok: true });
|
|
220
|
+
} catch {
|
|
221
|
+
return Response.json({ error: "Invalid request" }, { status: 400 });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// API: Serve images (local paths or temp uploads)
|
|
226
|
+
if (url.pathname === "/api/image") {
|
|
227
|
+
return handleImage(req);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// API: Serve a linked markdown document
|
|
231
|
+
// Inject source file's directory as base for relative path resolution.
|
|
232
|
+
// Skip base injection for URL annotations — there's no local directory to resolve against.
|
|
233
|
+
if (url.pathname === "/api/doc" && req.method === "GET") {
|
|
234
|
+
if (!url.searchParams.has("base") && !/^https?:\/\//i.test(filePath)) {
|
|
235
|
+
const docUrl = new URL(req.url);
|
|
236
|
+
docUrl.searchParams.set("base", dirname(filePath));
|
|
237
|
+
return handleDoc(new Request(docUrl.toString()));
|
|
238
|
+
}
|
|
239
|
+
return handleDoc(req);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
// API: Batch existence check for code-file paths the renderer detected
|
|
243
|
+
if (url.pathname === "/api/doc/exists" && req.method === "POST") {
|
|
244
|
+
return handleDocExists(req);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// API: Detect Obsidian vaults
|
|
248
|
+
if (url.pathname === "/api/obsidian/vaults") {
|
|
249
|
+
return handleObsidianVaults();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// API: List Obsidian vault files as a tree
|
|
253
|
+
if (url.pathname === "/api/reference/obsidian/files" && req.method === "GET") {
|
|
254
|
+
return handleObsidianFiles(req);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// API: Read an Obsidian vault document
|
|
258
|
+
if (url.pathname === "/api/reference/obsidian/doc" && req.method === "GET") {
|
|
259
|
+
return handleObsidianDoc(req);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// API: List markdown files in a directory as a tree
|
|
263
|
+
if (url.pathname === "/api/reference/files" && req.method === "GET") {
|
|
264
|
+
return handleFileBrowserFiles(req);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// API: Upload image -> save to temp -> return path
|
|
268
|
+
if (url.pathname === "/api/upload" && req.method === "POST") {
|
|
269
|
+
return handleUpload(req);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// API: Annotation draft persistence
|
|
273
|
+
if (url.pathname === "/api/draft") {
|
|
274
|
+
if (req.method === "POST") return handleDraftSave(req, draftKey);
|
|
275
|
+
if (req.method === "DELETE") return handleDraftDelete(draftKey);
|
|
276
|
+
return handleDraftLoad(draftKey);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// API: External annotations (SSE-based, for any external tool)
|
|
280
|
+
const externalResponse = await externalAnnotations.handle(req, url, {
|
|
281
|
+
disableIdleTimeout: () => server.timeout(req, 0),
|
|
282
|
+
});
|
|
283
|
+
if (externalResponse) return externalResponse;
|
|
284
|
+
|
|
285
|
+
if (url.pathname.startsWith("/api/ai/")) {
|
|
286
|
+
const handler = aiRuntime.endpoints[url.pathname as keyof AIEndpoints];
|
|
287
|
+
if (handler) {
|
|
288
|
+
if (url.pathname === AI_QUERY_ENDPOINT) {
|
|
289
|
+
server.timeout(req, 0);
|
|
290
|
+
}
|
|
291
|
+
return handler(req);
|
|
292
|
+
}
|
|
293
|
+
return Response.json({ error: "Not found" }, { status: 404 });
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// API: Exit annotation session without feedback
|
|
297
|
+
if (url.pathname === "/api/exit" && req.method === "POST") {
|
|
298
|
+
deleteDraft(draftKey);
|
|
299
|
+
resolveDecision({ feedback: "", annotations: [], exit: true });
|
|
300
|
+
return Response.json({ ok: true });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// API: Approve the annotation session (review-gate UX)
|
|
304
|
+
if (url.pathname === "/api/approve" && req.method === "POST") {
|
|
305
|
+
deleteDraft(draftKey);
|
|
306
|
+
resolveDecision({ feedback: "", annotations: [], approved: true });
|
|
307
|
+
return Response.json({ ok: true });
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// API: Submit annotation feedback
|
|
311
|
+
if (url.pathname === "/api/feedback" && req.method === "POST") {
|
|
312
|
+
try {
|
|
313
|
+
const body = (await req.json()) as {
|
|
314
|
+
feedback: string;
|
|
315
|
+
annotations: unknown[];
|
|
316
|
+
selectedMessageId?: string;
|
|
317
|
+
feedbackScope?: "message" | "messages";
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
deleteDraft(draftKey);
|
|
321
|
+
resolveDecision({
|
|
322
|
+
feedback: body.feedback || "",
|
|
323
|
+
annotations: body.annotations || [],
|
|
324
|
+
selectedMessageId: body.selectedMessageId,
|
|
325
|
+
feedbackScope: body.feedbackScope,
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
return Response.json({ ok: true });
|
|
329
|
+
} catch (err) {
|
|
330
|
+
const message =
|
|
331
|
+
err instanceof Error
|
|
332
|
+
? err.message
|
|
333
|
+
: "Failed to process feedback";
|
|
334
|
+
return Response.json({ error: message }, { status: 500 });
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Favicon
|
|
339
|
+
if (url.pathname === "/favicon.svg") return handleFavicon();
|
|
340
|
+
|
|
341
|
+
// Serve embedded HTML for all other routes (SPA)
|
|
342
|
+
return new Response(htmlContent, {
|
|
343
|
+
headers: { "Content-Type": "text/html" },
|
|
344
|
+
});
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
error(err) {
|
|
348
|
+
console.error("[plannotator] Server error:", err);
|
|
349
|
+
return new Response(
|
|
350
|
+
`Internal Server Error: ${err instanceof Error ? err.message : String(err)}`,
|
|
351
|
+
{ status: 500, headers: { "Content-Type": "text/plain" } },
|
|
352
|
+
);
|
|
353
|
+
},
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
break; // Success, exit retry loop
|
|
357
|
+
} catch (err: unknown) {
|
|
358
|
+
const isAddressInUse =
|
|
359
|
+
err instanceof Error && err.message.includes("EADDRINUSE");
|
|
360
|
+
|
|
361
|
+
if (isAddressInUse && attempt < MAX_RETRIES) {
|
|
362
|
+
await Bun.sleep(RETRY_DELAY_MS);
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (isAddressInUse) {
|
|
367
|
+
const hint = isRemote
|
|
368
|
+
? " (set PLANNOTATOR_PORT to use different port)"
|
|
369
|
+
: "";
|
|
370
|
+
throw new Error(
|
|
371
|
+
`Port ${configuredPort} in use after ${MAX_RETRIES} retries${hint}`
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
throw err;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (!server) {
|
|
380
|
+
throw new Error("Failed to start server");
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const port = server.port!;
|
|
384
|
+
const serverUrl = `http://localhost:${port}`;
|
|
385
|
+
|
|
386
|
+
// Notify caller that server is ready
|
|
387
|
+
if (onReady) {
|
|
388
|
+
onReady(serverUrl, isRemote, port);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
return {
|
|
392
|
+
port,
|
|
393
|
+
url: serverUrl,
|
|
394
|
+
isRemote,
|
|
395
|
+
waitForDecision: () => decisionPromise,
|
|
396
|
+
stop: () => {
|
|
397
|
+
aiRuntime.dispose();
|
|
398
|
+
server.stop();
|
|
399
|
+
},
|
|
400
|
+
};
|
|
401
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { afterEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import { isNoOpBrowserSentinel, shouldTryRemoteBrowserFallback } from "./browser";
|
|
3
|
+
|
|
4
|
+
const savedEnv: Record<string, string | undefined> = {};
|
|
5
|
+
const envKeys = ["PLANNOTATOR_BROWSER", "BROWSER"];
|
|
6
|
+
|
|
7
|
+
function clearEnv() {
|
|
8
|
+
for (const key of envKeys) {
|
|
9
|
+
savedEnv[key] = process.env[key];
|
|
10
|
+
delete process.env[key];
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
for (const key of envKeys) {
|
|
16
|
+
if (savedEnv[key] !== undefined) {
|
|
17
|
+
process.env[key] = savedEnv[key];
|
|
18
|
+
} else {
|
|
19
|
+
delete process.env[key];
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
describe("shouldTryRemoteBrowserFallback", () => {
|
|
25
|
+
test("false for local sessions", () => {
|
|
26
|
+
clearEnv();
|
|
27
|
+
expect(shouldTryRemoteBrowserFallback(false)).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("true for remote sessions without browser handlers", () => {
|
|
31
|
+
clearEnv();
|
|
32
|
+
expect(shouldTryRemoteBrowserFallback(true)).toBe(true);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("false for remote sessions with BROWSER configured", () => {
|
|
36
|
+
clearEnv();
|
|
37
|
+
process.env.BROWSER = "/usr/bin/browser";
|
|
38
|
+
expect(shouldTryRemoteBrowserFallback(true)).toBe(false);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("false for remote sessions with PLANNOTATOR_BROWSER configured", () => {
|
|
42
|
+
clearEnv();
|
|
43
|
+
process.env.PLANNOTATOR_BROWSER = "/usr/bin/browser";
|
|
44
|
+
expect(shouldTryRemoteBrowserFallback(true)).toBe(false);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
test("true for remote sessions when BROWSER is a no-op sentinel", () => {
|
|
48
|
+
clearEnv();
|
|
49
|
+
process.env.BROWSER = "true";
|
|
50
|
+
expect(shouldTryRemoteBrowserFallback(true)).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("true for remote sessions when PLANNOTATOR_BROWSER is a no-op sentinel", () => {
|
|
54
|
+
clearEnv();
|
|
55
|
+
process.env.PLANNOTATOR_BROWSER = "none";
|
|
56
|
+
expect(shouldTryRemoteBrowserFallback(true)).toBe(true);
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
describe("isNoOpBrowserSentinel", () => {
|
|
61
|
+
test("returns false for undefined and empty values", () => {
|
|
62
|
+
expect(isNoOpBrowserSentinel(undefined)).toBe(false);
|
|
63
|
+
expect(isNoOpBrowserSentinel("")).toBe(false);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("recognizes no-op values case- and whitespace-insensitively", () => {
|
|
67
|
+
for (const value of [
|
|
68
|
+
"true",
|
|
69
|
+
"false",
|
|
70
|
+
"none",
|
|
71
|
+
":",
|
|
72
|
+
"0",
|
|
73
|
+
"1",
|
|
74
|
+
"TRUE",
|
|
75
|
+
" none ",
|
|
76
|
+
]) {
|
|
77
|
+
expect(isNoOpBrowserSentinel(value)).toBe(true);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("does not flag real browser handlers or explicit command paths", () => {
|
|
82
|
+
expect(isNoOpBrowserSentinel("/usr/bin/firefox")).toBe(false);
|
|
83
|
+
expect(isNoOpBrowserSentinel("Google Chrome")).toBe(false);
|
|
84
|
+
expect(isNoOpBrowserSentinel("open")).toBe(false);
|
|
85
|
+
expect(isNoOpBrowserSentinel("/usr/bin/true")).toBe(false);
|
|
86
|
+
});
|
|
87
|
+
});
|