@doxbrix/doxloop 0.1.4 → 0.2.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/CHANGELOG.md +1153 -0
- package/README.md +422 -226
- package/assets/doxbrix-preview.css +77 -15
- package/contracts/agent-events-v1.schema.json +1 -0
- package/contracts/coverage-v1.schema.json +59 -0
- package/contracts/drift-v1.schema.json +1 -0
- package/contracts/evaluation-v1.schema.json +62 -0
- package/contracts/quality-codes-v1.json +1 -0
- package/contracts/quality-report-v1.schema.json +1 -0
- package/contracts/validation-v1.schema.json +1 -0
- package/dist/agent-failure.d.ts +36 -0
- package/dist/agent-failure.js +98 -0
- package/dist/agent-isolation.d.ts +17 -0
- package/dist/agent-isolation.js +94 -0
- package/dist/agent-log.d.ts +197 -0
- package/dist/agent-log.js +1094 -0
- package/dist/agent-process.d.ts +63 -0
- package/dist/agent-process.js +128 -0
- package/dist/agent-reply.d.ts +47 -0
- package/dist/agent-reply.js +315 -0
- package/dist/agents.d.ts +40 -4
- package/dist/agents.js +115 -11
- package/dist/api-coverage.d.ts +11 -0
- package/dist/api-coverage.js +72 -0
- package/dist/application-probe.d.ts +28 -0
- package/dist/application-probe.js +93 -0
- package/dist/application-url.d.ts +14 -0
- package/dist/application-url.js +26 -0
- package/dist/args.js +15 -0
- package/dist/artifact-deploy.d.ts +9 -0
- package/dist/artifact-deploy.js +28 -8
- package/dist/assets.d.ts +72 -0
- package/dist/assets.js +382 -0
- package/dist/auth.d.ts +10 -0
- package/dist/auth.js +38 -9
- package/dist/author.d.ts +206 -4
- package/dist/author.js +1650 -52
- package/dist/authoring-batches.d.ts +190 -0
- package/dist/authoring-batches.js +489 -0
- package/dist/authoring-postpass.d.ts +79 -0
- package/dist/authoring-postpass.js +1289 -0
- package/dist/authoring-progress.d.ts +95 -0
- package/dist/authoring-progress.js +269 -0
- package/dist/autosync.d.ts +56 -0
- package/dist/autosync.js +442 -0
- package/dist/batch-artifacts.d.ts +81 -0
- package/dist/batch-artifacts.js +412 -0
- package/dist/batch-limits.d.ts +37 -0
- package/dist/batch-limits.js +80 -0
- package/dist/branding.d.ts +41 -0
- package/dist/branding.js +229 -0
- package/dist/bundle-upload.d.ts +26 -0
- package/dist/bundle-upload.js +99 -0
- package/dist/capture-auth.d.ts +95 -0
- package/dist/capture-auth.js +204 -0
- package/dist/capture.d.ts +48 -0
- package/dist/capture.js +20 -1
- package/dist/cli.js +839 -32
- package/dist/content-links.d.ts +10 -0
- package/dist/content-links.js +49 -0
- package/dist/contract-validation.d.ts +4 -0
- package/dist/contract-validation.js +25 -0
- package/dist/coverage-actions.d.ts +8 -0
- package/dist/coverage-actions.js +71 -0
- package/dist/coverage-resolutions.d.ts +16 -0
- package/dist/coverage-resolutions.js +58 -0
- package/dist/db.d.ts +37 -0
- package/dist/db.js +288 -0
- package/dist/demo.d.ts +12 -0
- package/dist/demo.js +122 -0
- package/dist/deploy-credentials.d.ts +7 -0
- package/dist/deploy-credentials.js +76 -0
- package/dist/deploy-targets/github-pages.d.ts +3 -0
- package/dist/deploy-targets/github-pages.js +100 -0
- package/dist/deploy-targets/index.d.ts +8 -0
- package/dist/deploy-targets/index.js +78 -0
- package/dist/deploy-targets/netlify.d.ts +3 -0
- package/dist/deploy-targets/netlify.js +49 -0
- package/dist/deploy-targets/types.d.ts +32 -0
- package/dist/deploy-targets/types.js +2 -0
- package/dist/deploy-targets/vercel.d.ts +3 -0
- package/dist/deploy-targets/vercel.js +95 -0
- package/dist/deploy.d.ts +9 -0
- package/dist/deploy.js +87 -13
- package/dist/deterministic-capture.d.ts +90 -0
- package/dist/deterministic-capture.js +435 -0
- package/dist/direct-edit.d.ts +40 -0
- package/dist/direct-edit.js +159 -0
- package/dist/docs-crawl.d.ts +86 -0
- package/dist/docs-crawl.js +536 -0
- package/dist/docs-site.d.ts +49 -0
- package/dist/docs-site.js +233 -0
- package/dist/doctor.js +8 -0
- package/dist/documentation-collections.d.ts +23 -0
- package/dist/documentation-collections.js +191 -0
- package/dist/documentation-plan.d.ts +253 -0
- package/dist/documentation-plan.js +2563 -0
- package/dist/doxbrix-build.d.ts +19 -0
- package/dist/doxbrix-build.js +167 -0
- package/dist/doxbrix-markdown.d.ts +9 -0
- package/dist/doxbrix-markdown.js +115 -14
- package/dist/drift.d.ts +10 -0
- package/dist/drift.js +164 -0
- package/dist/evaluation.d.ts +46 -0
- package/dist/evaluation.js +113 -0
- package/dist/evidence-pack.d.ts +47 -0
- package/dist/evidence-pack.js +358 -0
- package/dist/evidence.d.ts +25 -0
- package/dist/evidence.js +175 -0
- package/dist/fs.d.ts +8 -2
- package/dist/fs.js +41 -11
- package/dist/generator-api.d.ts +89 -0
- package/dist/generator-preflight.d.ts +27 -0
- package/dist/generator-preflight.js +105 -0
- package/dist/generator-runtime.d.ts +7 -0
- package/dist/generator-runtime.js +17 -1
- package/dist/generators.d.ts +24 -2
- package/dist/generators.js +54 -1
- package/dist/git-delivery.d.ts +17 -0
- package/dist/git-delivery.js +123 -0
- package/dist/globs.d.ts +16 -0
- package/dist/globs.js +65 -0
- package/dist/glossary.d.ts +26 -0
- package/dist/glossary.js +179 -0
- package/dist/history.d.ts +106 -0
- package/dist/history.js +600 -0
- package/dist/html-markdown.d.ts +46 -0
- package/dist/html-markdown.js +423 -0
- package/dist/interactive.js +16 -15
- package/dist/job-events.d.ts +74 -0
- package/dist/job-events.js +377 -0
- package/dist/keep-awake.d.ts +50 -0
- package/dist/keep-awake.js +123 -0
- package/dist/local-source-snapshot.d.ts +20 -0
- package/dist/local-source-snapshot.js +61 -0
- package/dist/mintlify-detect.d.ts +3 -0
- package/dist/mintlify-detect.js +18 -0
- package/dist/mintlify-import.d.ts +75 -0
- package/dist/mintlify-import.js +190 -0
- package/dist/navigation.d.ts +98 -0
- package/dist/navigation.js +310 -0
- package/dist/openapi.d.ts +60 -0
- package/dist/openapi.js +439 -0
- package/dist/page-editor-bridge.d.ts +3 -0
- package/dist/page-editor-bridge.js +109 -0
- package/dist/page-editor-preview.d.ts +10 -0
- package/dist/page-editor-preview.js +55 -0
- package/dist/page-extension.d.ts +9 -0
- package/dist/page-extension.js +15 -0
- package/dist/page-metadata.d.ts +28 -0
- package/dist/page-metadata.js +166 -0
- package/dist/page-operations.d.ts +34 -0
- package/dist/page-operations.js +215 -0
- package/dist/page-routes.d.ts +4 -0
- package/dist/page-routes.js +61 -0
- package/dist/pages.d.ts +20 -0
- package/dist/pages.js +184 -0
- package/dist/plan-generator.d.ts +3 -0
- package/dist/plan-generator.js +21 -0
- package/dist/plan-navigation.d.ts +11 -0
- package/dist/plan-navigation.js +30 -0
- package/dist/planning-captures.d.ts +20 -0
- package/dist/planning-captures.js +143 -0
- package/dist/planning-research.d.ts +135 -0
- package/dist/planning-research.js +472 -0
- package/dist/planning-triage.d.ts +23 -0
- package/dist/planning-triage.js +131 -0
- package/dist/preview.d.ts +24 -0
- package/dist/preview.js +280 -29
- package/dist/project-detect.d.ts +36 -0
- package/dist/project-detect.js +251 -0
- package/dist/project-import.d.ts +54 -0
- package/dist/project-import.js +157 -0
- package/dist/project-lock.d.ts +6 -0
- package/dist/project-lock.js +96 -0
- package/dist/project-registry.d.ts +25 -0
- package/dist/project-registry.js +79 -0
- package/dist/project.d.ts +35 -3
- package/dist/project.js +388 -38
- package/dist/prompts.d.ts +9 -0
- package/dist/prompts.js +33 -4
- package/dist/proposal-replay.d.ts +32 -0
- package/dist/proposal-replay.js +99 -0
- package/dist/quality-claims.d.ts +8 -0
- package/dist/quality-claims.js +168 -0
- package/dist/quality-config.d.ts +5 -0
- package/dist/quality-config.js +84 -0
- package/dist/quality-contract.d.ts +37 -0
- package/dist/quality-contract.js +46 -0
- package/dist/quality-examples.d.ts +4 -0
- package/dist/quality-examples.js +233 -0
- package/dist/quality-gates.d.ts +16 -0
- package/dist/quality-gates.js +192 -0
- package/dist/quality-links.d.ts +7 -0
- package/dist/quality-links.js +149 -0
- package/dist/quality-lint.d.ts +6 -0
- package/dist/quality-lint.js +124 -0
- package/dist/quality-rendered.d.ts +11 -0
- package/dist/quality-rendered.js +222 -0
- package/dist/quality-schema.d.ts +3 -0
- package/dist/quality-schema.js +71 -0
- package/dist/release-notes.d.ts +44 -0
- package/dist/release-notes.js +183 -0
- package/dist/remote-monitor.d.ts +16 -0
- package/dist/remote-monitor.js +74 -0
- package/dist/remote-source.d.ts +34 -0
- package/dist/remote-source.js +426 -0
- package/dist/review-diff.d.ts +82 -0
- package/dist/review-diff.js +400 -0
- package/dist/review-learning.d.ts +11 -0
- package/dist/review-learning.js +60 -0
- package/dist/review-render.d.ts +38 -0
- package/dist/review-render.js +224 -0
- package/dist/review-report.d.ts +9 -0
- package/dist/review-report.js +89 -0
- package/dist/review-ui.d.ts +14 -0
- package/dist/review-ui.js +1248 -0
- package/dist/schedule.d.ts +78 -0
- package/dist/schedule.js +480 -0
- package/dist/screen-capture-provider.d.ts +52 -0
- package/dist/screen-capture-provider.js +218 -0
- package/dist/screenshot-workflow.d.ts +167 -0
- package/dist/screenshot-workflow.js +1237 -0
- package/dist/settings.d.ts +1 -1
- package/dist/settings.js +95 -7
- package/dist/site-export.d.ts +18 -0
- package/dist/site-export.js +87 -0
- package/dist/source-connectors.d.ts +33 -0
- package/dist/source-connectors.js +268 -0
- package/dist/source-discovery.d.ts +132 -0
- package/dist/source-discovery.js +823 -0
- package/dist/source-intelligence.d.ts +9 -0
- package/dist/source-intelligence.js +306 -0
- package/dist/sync-review.d.ts +28 -0
- package/dist/sync-review.js +264 -0
- package/dist/sync-runs.d.ts +192 -0
- package/dist/sync-runs.js +2244 -0
- package/dist/sync.d.ts +35 -0
- package/dist/sync.js +298 -32
- package/dist/text-diff.d.ts +9 -0
- package/dist/text-diff.js +59 -0
- package/dist/types.d.ts +946 -1
- package/dist/ui/assets/doxloop-logo-light-De7Nx7j7.png +0 -0
- package/dist/ui/assets/index-BHBYU2aG.css +1 -0
- package/dist/ui/assets/index-Cq3RPQiC.js +33 -0
- package/dist/ui/index.html +18 -0
- package/dist/ui-server.d.ts +83 -0
- package/dist/ui-server.js +3532 -0
- package/dist/usage-budget.d.ts +28 -0
- package/dist/usage-budget.js +90 -0
- package/dist/validation.d.ts +25 -1
- package/dist/validation.js +312 -26
- package/dist/workspace-tools.d.ts +54 -0
- package/dist/workspace-tools.js +123 -0
- package/docs/agent-compatibility.md +50 -28
- package/docs/ci-and-automation.md +105 -66
- package/docs/doxbrix-http-api.md +8 -1
- package/docs/existing-documentation.md +80 -0
- package/docs/generation-performance.md +108 -0
- package/docs/generator-authoring.md +68 -5
- package/docs/generator-selection.md +50 -13
- package/docs/mintlify-import.md +71 -0
- package/docs/openapi-security.md +25 -0
- package/docs/project-format.md +331 -36
- package/docs/release-quality.md +158 -0
- package/docs/releasing.md +72 -0
- package/docs/review-workflows.md +51 -0
- package/docs/security-model.md +102 -39
- package/docs/troubleshooting.md +226 -93
- package/package.json +46 -15
- package/scripts/test-auto-screenshot.mjs +172 -0
- package/skills/doxloop-authoring/SKILL.md +242 -361
- package/skills/doxloop-authoring/references/existing-documentation.md +94 -0
- package/skills/doxloop-authoring/references/navigation-architecture.md +18 -7
- package/skills/doxloop-authoring/references/page-depth.md +169 -0
- package/skills/doxloop-authoring/references/project-format.md +106 -7
- package/skills/doxloop-authoring/references/quality.md +10 -0
- package/skills/doxloop-authoring/references/screenshot-manifest.md +113 -0
- package/skills/doxloop-authoring/references/screenshots.md +149 -235
- package/skills/doxloop-authoring/references/workflows.md +76 -0
- package/skills/doxloop-doxbrix/SKILL.md +38 -22
- package/skills/doxloop-doxbrix/references/api-endpoints.md +15 -14
- package/skills/doxloop-doxbrix/references/components.md +30 -3
- package/skills/doxloop-doxbrix/references/manifest.md +3 -2
- package/vendor/doxbrix-import/LICENSE +202 -0
- package/vendor/doxbrix-import/README.md +17 -0
- package/vendor/doxbrix-import/UPSTREAM.json +47 -0
- package/vendor/doxbrix-import/dist/docs/frontmatter.d.ts +13 -0
- package/vendor/doxbrix-import/dist/docs/frontmatter.js +83 -0
- package/vendor/doxbrix-import/dist/docs/import.d.ts +21 -0
- package/vendor/doxbrix-import/dist/docs/import.js +147 -0
- package/vendor/doxbrix-import/dist/docs/manifest.d.ts +163 -0
- package/vendor/doxbrix-import/dist/docs/manifest.js +64 -0
- package/vendor/doxbrix-import/dist/docs/project.d.ts +25 -0
- package/vendor/doxbrix-import/dist/docs/project.js +77 -0
- package/vendor/doxbrix-import/dist/docs/starter.d.ts +4 -0
- package/vendor/doxbrix-import/dist/docs/starter.js +11 -0
- package/vendor/doxbrix-import/dist/importer.d.ts +227 -0
- package/vendor/doxbrix-import/dist/importer.js +1567 -0
- package/vendor/doxbrix-import/dist/mintlify-openapi.d.ts +37 -0
- package/vendor/doxbrix-import/dist/mintlify-openapi.js +305 -0
- package/vendor/doxbrix-import/dist/safe-path.d.ts +9 -0
- package/vendor/doxbrix-import/dist/safe-path.js +47 -0
- package/dist/agents.d.ts.map +0 -1
- package/dist/agents.js.map +0 -1
- package/dist/args.d.ts.map +0 -1
- package/dist/args.js.map +0 -1
- package/dist/artifact-deploy.d.ts.map +0 -1
- package/dist/artifact-deploy.js.map +0 -1
- package/dist/auth.d.ts.map +0 -1
- package/dist/auth.js.map +0 -1
- package/dist/author.d.ts.map +0 -1
- package/dist/author.js.map +0 -1
- package/dist/capture.d.ts.map +0 -1
- package/dist/capture.js.map +0 -1
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/deploy.d.ts.map +0 -1
- package/dist/deploy.js.map +0 -1
- package/dist/deployment-visibility.d.ts.map +0 -1
- package/dist/deployment-visibility.js.map +0 -1
- package/dist/doctor.d.ts.map +0 -1
- package/dist/doctor.js.map +0 -1
- package/dist/doxbrix-markdown.d.ts.map +0 -1
- package/dist/doxbrix-markdown.js.map +0 -1
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js.map +0 -1
- package/dist/fs.d.ts.map +0 -1
- package/dist/fs.js.map +0 -1
- package/dist/generator-api.d.ts.map +0 -1
- package/dist/generator-api.js.map +0 -1
- package/dist/generator-manager.d.ts.map +0 -1
- package/dist/generator-manager.js.map +0 -1
- package/dist/generator-runtime.d.ts.map +0 -1
- package/dist/generator-runtime.js.map +0 -1
- package/dist/generators.d.ts.map +0 -1
- package/dist/generators.js.map +0 -1
- package/dist/interactive.d.ts.map +0 -1
- package/dist/interactive.js.map +0 -1
- package/dist/preview.d.ts.map +0 -1
- package/dist/preview.js.map +0 -1
- package/dist/progress.d.ts.map +0 -1
- package/dist/progress.js.map +0 -1
- package/dist/project.d.ts.map +0 -1
- package/dist/project.js.map +0 -1
- package/dist/prompts.d.ts.map +0 -1
- package/dist/prompts.js.map +0 -1
- package/dist/settings.d.ts.map +0 -1
- package/dist/settings.js.map +0 -1
- package/dist/sync.d.ts.map +0 -1
- package/dist/sync.js.map +0 -1
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js.map +0 -1
- package/dist/version.d.ts.map +0 -1
- package/dist/version.js.map +0 -1
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type ChildProcess, type StdioOptions } from 'node:child_process';
|
|
2
|
+
/**
|
|
3
|
+
* A documentation agent is never alone: Claude and Codex start the capture
|
|
4
|
+
* MCP server, which starts a browser. Stopping only the agent left those
|
|
5
|
+
* helpers running and writing screenshots into a workspace the user had
|
|
6
|
+
* already abandoned. An unattended agent therefore runs in its own process
|
|
7
|
+
* group so the whole tree can be stopped together.
|
|
8
|
+
*/
|
|
9
|
+
export interface AgentProcess {
|
|
10
|
+
readonly child: ChildProcess;
|
|
11
|
+
/** True when the agent leads its own process group and helpers stop with it. */
|
|
12
|
+
readonly isolated: boolean;
|
|
13
|
+
/** Resolves once the agent has exited, however it exited. */
|
|
14
|
+
readonly exited: Promise<AgentExit>;
|
|
15
|
+
/**
|
|
16
|
+
* Ask the agent to stop, then force it after the grace period. Resolves once
|
|
17
|
+
* the agent process is gone. Safe to call more than once.
|
|
18
|
+
*/
|
|
19
|
+
stop(options?: {
|
|
20
|
+
signal?: NodeJS.Signals;
|
|
21
|
+
graceMs?: number;
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export interface AgentExit {
|
|
25
|
+
code: number | null;
|
|
26
|
+
signal: NodeJS.Signals | null;
|
|
27
|
+
/** Set when the process could not be started at all. */
|
|
28
|
+
error?: Error;
|
|
29
|
+
}
|
|
30
|
+
export declare const AGENT_STOP_GRACE_MS = 10000;
|
|
31
|
+
export declare function spawnAgentProcess(executable: string, args: readonly string[], options: {
|
|
32
|
+
cwd: string;
|
|
33
|
+
env?: NodeJS.ProcessEnv;
|
|
34
|
+
stdio: StdioOptions;
|
|
35
|
+
/**
|
|
36
|
+
* Put the agent in its own process group. Only unattended runs qualify: an
|
|
37
|
+
* interactive agent must stay in the terminal's foreground group so it can
|
|
38
|
+
* own the keyboard and receive Ctrl+C itself.
|
|
39
|
+
*/
|
|
40
|
+
isolate?: boolean;
|
|
41
|
+
}): AgentProcess;
|
|
42
|
+
/**
|
|
43
|
+
* Deliver a signal to an agent and everything it started. On Windows there is
|
|
44
|
+
* no process group to signal, so the tree is terminated through taskkill.
|
|
45
|
+
*/
|
|
46
|
+
export declare function signalTree(child: ChildProcess, isolated: boolean, signal: NodeJS.Signals): void;
|
|
47
|
+
/**
|
|
48
|
+
* Forward a termination signal received by this process to the running agent,
|
|
49
|
+
* wait for it to stop, then exit with the conventional 128+signal status. The
|
|
50
|
+
* Doxloop UI stops a run by signalling the CLI it started; without this the
|
|
51
|
+
* CLI died at once and the agent kept working on a run nobody was watching.
|
|
52
|
+
* Returns a function that removes the handlers once the agent has finished.
|
|
53
|
+
*/
|
|
54
|
+
export declare function forwardTerminationSignals(agent: AgentProcess, options: {
|
|
55
|
+
label: string;
|
|
56
|
+
signals?: readonly NodeJS.Signals[];
|
|
57
|
+
graceMs?: number;
|
|
58
|
+
/** Runs after the agent stopped and before the process exits. */
|
|
59
|
+
onStopped?: () => Promise<void> | void;
|
|
60
|
+
log?: (line: string) => void;
|
|
61
|
+
exit?: (code: number) => void;
|
|
62
|
+
}): () => void;
|
|
63
|
+
//# sourceMappingURL=agent-process.d.ts.map
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { spawn as nodeSpawn } from 'node:child_process';
|
|
2
|
+
import { constants as osConstants } from 'node:os';
|
|
3
|
+
import crossSpawn from 'cross-spawn';
|
|
4
|
+
export const AGENT_STOP_GRACE_MS = 10_000;
|
|
5
|
+
export function spawnAgentProcess(executable, args, options) {
|
|
6
|
+
const isolated = options.isolate === true && process.platform !== 'win32';
|
|
7
|
+
const child = crossSpawn(executable, [...args], {
|
|
8
|
+
cwd: options.cwd,
|
|
9
|
+
env: options.env ?? process.env,
|
|
10
|
+
stdio: options.stdio,
|
|
11
|
+
...(isolated ? { detached: true } : {}),
|
|
12
|
+
});
|
|
13
|
+
let settled = false;
|
|
14
|
+
const exited = new Promise((resolveExit) => {
|
|
15
|
+
child.once('error', (error) => {
|
|
16
|
+
if (settled)
|
|
17
|
+
return;
|
|
18
|
+
settled = true;
|
|
19
|
+
resolveExit({ code: null, signal: null, error });
|
|
20
|
+
});
|
|
21
|
+
child.once('exit', (code, signal) => {
|
|
22
|
+
if (settled)
|
|
23
|
+
return;
|
|
24
|
+
settled = true;
|
|
25
|
+
resolveExit({ code, signal });
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
let stopping;
|
|
29
|
+
const stop = (stopOptions = {}) => {
|
|
30
|
+
if (stopping)
|
|
31
|
+
return stopping;
|
|
32
|
+
stopping = (async () => {
|
|
33
|
+
if (settled) {
|
|
34
|
+
if (isolated)
|
|
35
|
+
signalTree(child, isolated, 'SIGKILL');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const grace = stopOptions.graceMs ?? AGENT_STOP_GRACE_MS;
|
|
39
|
+
signalTree(child, isolated, stopOptions.signal ?? 'SIGTERM');
|
|
40
|
+
const escalation = setTimeout(() => {
|
|
41
|
+
if (!settled)
|
|
42
|
+
signalTree(child, isolated, 'SIGKILL');
|
|
43
|
+
}, grace);
|
|
44
|
+
escalation.unref?.();
|
|
45
|
+
await exited;
|
|
46
|
+
clearTimeout(escalation);
|
|
47
|
+
// The agent may have exited politely while a helper it started did not.
|
|
48
|
+
if (isolated)
|
|
49
|
+
signalTree(child, isolated, 'SIGKILL');
|
|
50
|
+
})();
|
|
51
|
+
return stopping;
|
|
52
|
+
};
|
|
53
|
+
return { child, isolated, exited, stop };
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Deliver a signal to an agent and everything it started. On Windows there is
|
|
57
|
+
* no process group to signal, so the tree is terminated through taskkill.
|
|
58
|
+
*/
|
|
59
|
+
export function signalTree(child, isolated, signal) {
|
|
60
|
+
const pid = child.pid;
|
|
61
|
+
if (!pid)
|
|
62
|
+
return;
|
|
63
|
+
if (process.platform === 'win32') {
|
|
64
|
+
try {
|
|
65
|
+
const killer = nodeSpawn('taskkill', ['/pid', String(pid), '/T', '/F'], { stdio: 'ignore', windowsHide: true });
|
|
66
|
+
killer.once('error', () => { try {
|
|
67
|
+
child.kill();
|
|
68
|
+
}
|
|
69
|
+
catch { /* already gone */ } });
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
try {
|
|
73
|
+
child.kill();
|
|
74
|
+
}
|
|
75
|
+
catch { /* already gone */ }
|
|
76
|
+
}
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
if (isolated) {
|
|
80
|
+
try {
|
|
81
|
+
process.kill(-pid, signal);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
// The group is gone or was never created; fall back to the process itself.
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
child.kill(signal);
|
|
90
|
+
}
|
|
91
|
+
catch { /* already gone */ }
|
|
92
|
+
}
|
|
93
|
+
const SIGNAL_EXIT_CODES = {
|
|
94
|
+
SIGINT: 128 + osConstants.signals.SIGINT,
|
|
95
|
+
SIGTERM: 128 + osConstants.signals.SIGTERM,
|
|
96
|
+
SIGHUP: 128 + osConstants.signals.SIGHUP,
|
|
97
|
+
};
|
|
98
|
+
/**
|
|
99
|
+
* Forward a termination signal received by this process to the running agent,
|
|
100
|
+
* wait for it to stop, then exit with the conventional 128+signal status. The
|
|
101
|
+
* Doxloop UI stops a run by signalling the CLI it started; without this the
|
|
102
|
+
* CLI died at once and the agent kept working on a run nobody was watching.
|
|
103
|
+
* Returns a function that removes the handlers once the agent has finished.
|
|
104
|
+
*/
|
|
105
|
+
export function forwardTerminationSignals(agent, options) {
|
|
106
|
+
const signals = options.signals ?? ['SIGTERM', 'SIGINT'];
|
|
107
|
+
const log = options.log ?? ((line) => process.stderr.write(`${line}\n`));
|
|
108
|
+
const exit = options.exit ?? ((code) => process.exit(code));
|
|
109
|
+
let forwarding = false;
|
|
110
|
+
const handler = (signal) => {
|
|
111
|
+
if (forwarding)
|
|
112
|
+
return;
|
|
113
|
+
forwarding = true;
|
|
114
|
+
log(`Received ${signal}. Stopping ${options.label} and any capture browser it started…`);
|
|
115
|
+
void agent
|
|
116
|
+
.stop({ graceMs: options.graceMs ?? AGENT_STOP_GRACE_MS })
|
|
117
|
+
.then(() => options.onStopped?.())
|
|
118
|
+
.catch(() => undefined)
|
|
119
|
+
.then(() => exit(SIGNAL_EXIT_CODES[signal] ?? 1));
|
|
120
|
+
};
|
|
121
|
+
for (const signal of signals)
|
|
122
|
+
process.on(signal, handler);
|
|
123
|
+
return () => {
|
|
124
|
+
for (const signal of signals)
|
|
125
|
+
process.off(signal, handler);
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=agent-process.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { AgentName } from './types.js';
|
|
2
|
+
export declare function looksLikeDocumentationPlan(value: unknown): boolean;
|
|
3
|
+
export interface TaggedJsonContract {
|
|
4
|
+
/** Tag name without angle brackets, for example `doxloop-plan`. */
|
|
5
|
+
tag: string;
|
|
6
|
+
/** Whether a parsed candidate is the answer rather than a quoted example. */
|
|
7
|
+
accept: (value: unknown) => boolean;
|
|
8
|
+
/** Whether a candidate's text was clearly trying to be the answer, for defect reporting. */
|
|
9
|
+
attempted: (text: string) => boolean;
|
|
10
|
+
/** How the answer is named in messages: "documentation-plan", "research brief". */
|
|
11
|
+
noun: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* The agent's own final reply, read out of its machine-readable stream:
|
|
15
|
+
* Claude's `result` event, Codex's completed `agent_message` items, and
|
|
16
|
+
* Gemini's assistant `message` events. Undefined when the output is not a
|
|
17
|
+
* stream, in which case the whole transcript is searched instead.
|
|
18
|
+
*/
|
|
19
|
+
export declare function agentReplyFromStream(raw: string, agent: AgentName): string | undefined;
|
|
20
|
+
export interface TaggedJsonReply<T = unknown> {
|
|
21
|
+
value: T;
|
|
22
|
+
/** Deterministic fixes applied to read the reply, worded for a reviewer. */
|
|
23
|
+
repairs: string[];
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Read the object an agent was asked to return inside `<tag>…</tag>`. The
|
|
27
|
+
* agreed block wins outright when the agent honors it; otherwise the whole
|
|
28
|
+
* reply is searched, skipping anything that merely echoes the prompt.
|
|
29
|
+
* Returns undefined when nothing answer-shaped is found and no candidate was
|
|
30
|
+
* clearly trying to be the answer, so callers can fall back to another
|
|
31
|
+
* contract; throws when a candidate was found but is malformed.
|
|
32
|
+
*/
|
|
33
|
+
export declare function readTaggedJson<T = unknown>(raw: string, agent: AgentName, contract: TaggedJsonContract, prompt?: string): TaggedJsonReply<T> | undefined;
|
|
34
|
+
export interface PlanReply {
|
|
35
|
+
plan: unknown;
|
|
36
|
+
/** Deterministic fixes applied to read the reply, worded for the plan review. */
|
|
37
|
+
repairs: string[];
|
|
38
|
+
}
|
|
39
|
+
export declare function readPlanOutput(raw: string, agent: AgentName, prompt?: string): PlanReply;
|
|
40
|
+
export declare function extractPlanOutput(raw: string, agent: AgentName, prompt?: string): unknown;
|
|
41
|
+
/**
|
|
42
|
+
* A corrective pass answers with the pages it changed inside
|
|
43
|
+
* `<doxloop-plan-patch>`. Undefined when the reply carries no patch at all
|
|
44
|
+
* (an agent that resent the whole plan is read with `readPlanOutput`).
|
|
45
|
+
*/
|
|
46
|
+
export declare function readPlanPatchOutput(raw: string, agent: AgentName, prompt?: string): TaggedJsonReply | undefined;
|
|
47
|
+
//# sourceMappingURL=agent-reply.d.ts.map
|
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { compactLogText } from './agent-log.js';
|
|
2
|
+
import { DoxloopError } from './errors.js';
|
|
3
|
+
/**
|
|
4
|
+
* Reading one JSON object out of an agent's reply. Agents wrap the object in
|
|
5
|
+
* an agreed tag (`<doxloop-plan>`, `<doxloop-brief>`), but they also echo the
|
|
6
|
+
* instructions they were sent, quote the reference files they read, and drop
|
|
7
|
+
* the closing brackets of very long objects. Everything here exists to read
|
|
8
|
+
* the answer anyway without ever inventing content: a reply that is complete
|
|
9
|
+
* apart from its closers is closed and the fix reported; a reply cut off
|
|
10
|
+
* inside a value is refused with the exact defect.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Fields that only a documentation plan carries. An agent transcript also
|
|
14
|
+
* contains the skill references it read, whose fenced examples (a
|
|
15
|
+
* `.doxloop/project.json`, an evidence map) parse as perfectly valid JSON.
|
|
16
|
+
*/
|
|
17
|
+
const PLAN_SIGNAL_KEYS = [
|
|
18
|
+
'productProfile',
|
|
19
|
+
'summary',
|
|
20
|
+
'audiences',
|
|
21
|
+
'outcomes',
|
|
22
|
+
'capabilities',
|
|
23
|
+
'navigation',
|
|
24
|
+
'questions',
|
|
25
|
+
'instructions',
|
|
26
|
+
'experienceLevel',
|
|
27
|
+
'estimatedPages',
|
|
28
|
+
'estimatedEffort',
|
|
29
|
+
'preferredExamples',
|
|
30
|
+
'styleGuide',
|
|
31
|
+
];
|
|
32
|
+
export function looksLikeDocumentationPlan(value) {
|
|
33
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
34
|
+
return false;
|
|
35
|
+
const candidate = value;
|
|
36
|
+
const signals = PLAN_SIGNAL_KEYS.filter((key) => key in candidate).length;
|
|
37
|
+
// A plan lists planned pages as an array of page objects. An evidence map
|
|
38
|
+
// keys `pages` by path, a project manifest has no planned pages at all, and
|
|
39
|
+
// the plan's own nested objects (an existing-documentation finding or
|
|
40
|
+
// assessment) carry a `pages` array too but almost none of the plan's other
|
|
41
|
+
// top-level fields, so a pages array alone is not enough.
|
|
42
|
+
if (Array.isArray(candidate.pages) && candidate.pages.every((page) => page && typeof page === 'object') && signals >= 2)
|
|
43
|
+
return true;
|
|
44
|
+
return signals >= 3;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Read one balanced `{...}` starting at `start`, ignoring braces inside JSON
|
|
48
|
+
* strings. Each call starts fresh, so unbalanced braces or stray quotes in
|
|
49
|
+
* surrounding prose cannot desynchronize a later candidate.
|
|
50
|
+
*/
|
|
51
|
+
function objectTextAt(text, start) {
|
|
52
|
+
let depth = 0;
|
|
53
|
+
let inString = false;
|
|
54
|
+
let escaped = false;
|
|
55
|
+
for (let index = start; index < text.length; index += 1) {
|
|
56
|
+
const character = text[index];
|
|
57
|
+
if (inString) {
|
|
58
|
+
if (escaped)
|
|
59
|
+
escaped = false;
|
|
60
|
+
else if (character === '\\')
|
|
61
|
+
escaped = true;
|
|
62
|
+
else if (character === '"')
|
|
63
|
+
inString = false;
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (character === '"')
|
|
67
|
+
inString = true;
|
|
68
|
+
else if (character === '{')
|
|
69
|
+
depth += 1;
|
|
70
|
+
else if (character === '}') {
|
|
71
|
+
depth -= 1;
|
|
72
|
+
if (depth === 0)
|
|
73
|
+
return text.slice(start, index + 1);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Close an object whose final brackets are missing or wrong. Agents drop
|
|
80
|
+
* the outermost closers of a very long reply often enough (a 60k-character
|
|
81
|
+
* plan came back `]}` short twice in a row) that giving up would cost the
|
|
82
|
+
* whole planning run, and they also swap them: a product brief twice ended
|
|
83
|
+
* its top-level `unknowns` string array with `"}]}` where `"]}` was due.
|
|
84
|
+
* Appending the missing closers, or replacing a trailing run of wrong ones
|
|
85
|
+
* with the closers the open structures actually need, loses nothing when
|
|
86
|
+
* the reply ends on a completed value, optionally followed by a stray tag
|
|
87
|
+
* such as `</existingDocumentation>`. A reply that ends inside a string,
|
|
88
|
+
* after a comma, or partway through a value is cut off and is left alone,
|
|
89
|
+
* and so is a mismatched closer with more content after it: a plan silently
|
|
90
|
+
* shortened is worse than a plan reported missing.
|
|
91
|
+
*/
|
|
92
|
+
function closeObjectAt(text, start) {
|
|
93
|
+
const closers = [];
|
|
94
|
+
let inString = false;
|
|
95
|
+
let escaped = false;
|
|
96
|
+
let lastStructureEnd = -1;
|
|
97
|
+
for (let index = start; index < text.length; index += 1) {
|
|
98
|
+
const character = text[index];
|
|
99
|
+
if (inString) {
|
|
100
|
+
if (escaped)
|
|
101
|
+
escaped = false;
|
|
102
|
+
else if (character === '\\')
|
|
103
|
+
escaped = true;
|
|
104
|
+
else if (character === '"')
|
|
105
|
+
inString = false;
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
if (character === '"')
|
|
109
|
+
inString = true;
|
|
110
|
+
else if (character === '{')
|
|
111
|
+
closers.push('}');
|
|
112
|
+
else if (character === '[')
|
|
113
|
+
closers.push(']');
|
|
114
|
+
else if (character === '}' || character === ']') {
|
|
115
|
+
if (closers.at(-1) !== character) {
|
|
116
|
+
// The wrong closer. Only a tail made of nothing but closers (and a
|
|
117
|
+
// stray tag) after a completed value can be rewritten safely.
|
|
118
|
+
const tail = text.slice(index);
|
|
119
|
+
const written = tail.replace(/[^\]}]/g, '');
|
|
120
|
+
if (!/^[\s\]}]*(?:\s|<\/?[\w-]+>)*$/.test(tail) || !/(?:"|[\]}]|\d|true|false|null)\s*$/.test(text.slice(start, index)))
|
|
121
|
+
return undefined;
|
|
122
|
+
const suffix = closers.reverse().join('');
|
|
123
|
+
return { text: `${text.slice(start, index).trimEnd()}${suffix}`, closers: suffix, replaced: written };
|
|
124
|
+
}
|
|
125
|
+
closers.pop();
|
|
126
|
+
if (closers.length === 0)
|
|
127
|
+
return undefined;
|
|
128
|
+
lastStructureEnd = index;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (inString || lastStructureEnd < 0 || closers.length === 0)
|
|
132
|
+
return undefined;
|
|
133
|
+
if (!/^(?:\s|<\/?[\w-]+>)*$/.test(text.slice(lastStructureEnd + 1)))
|
|
134
|
+
return undefined;
|
|
135
|
+
const suffix = closers.reverse().join('');
|
|
136
|
+
return { text: `${text.slice(start, lastStructureEnd + 1)}${suffix}`, closers: suffix };
|
|
137
|
+
}
|
|
138
|
+
/** Compare transcript text to prompt text without depending on re-wrapping. */
|
|
139
|
+
function collapse(text) {
|
|
140
|
+
return text.replace(/\s+/g, ' ').trim();
|
|
141
|
+
}
|
|
142
|
+
/** Offsets where a JSON object plausibly begins, in document order. */
|
|
143
|
+
function objectStarts(text, tag) {
|
|
144
|
+
const starts = new Set();
|
|
145
|
+
const pattern = new RegExp(`(?:^|\\n|\`\`\`(?:json)?|<${tag}>)[ \\t\\r]*\\{`, 'gi');
|
|
146
|
+
for (const match of text.matchAll(pattern)) {
|
|
147
|
+
starts.add(match.index + match[0].lastIndexOf('{'));
|
|
148
|
+
}
|
|
149
|
+
return [...starts].sort((left, right) => left - right);
|
|
150
|
+
}
|
|
151
|
+
function closedReplyNote(closers, noun, replaced) {
|
|
152
|
+
if (replaced !== undefined) {
|
|
153
|
+
return `The ${noun} reply ended with the wrong closing brackets ("${replaced}" where "${closers}" closes the JSON object); Doxloop replaced them and read it. Check that its last entries are complete before relying on them.`;
|
|
154
|
+
}
|
|
155
|
+
const count = closers.length;
|
|
156
|
+
return `The ${noun} reply stopped ${count} closing bracket${count === 1 ? '' : 's'} short of a complete JSON object; Doxloop appended "${closers}" and read it. Check that its last entries are complete before relying on them.`;
|
|
157
|
+
}
|
|
158
|
+
function unclosedDefect(text) {
|
|
159
|
+
return `the JSON object is never closed and looks cut off; the reply ends with «${compactLogText(text.trimEnd().slice(-120), 120)}»`;
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* The last accepted object in `text`; the answer follows what was quoted.
|
|
163
|
+
* Also reports why the most answer-like candidate failed, so a malformed reply
|
|
164
|
+
* is distinguishable from one that never contained an answer at all.
|
|
165
|
+
*/
|
|
166
|
+
function lastAcceptedIn(text, contract, sent) {
|
|
167
|
+
const starts = objectStarts(text, contract.tag);
|
|
168
|
+
let defect;
|
|
169
|
+
for (let index = starts.length - 1; index >= 0; index -= 1) {
|
|
170
|
+
const start = starts[index];
|
|
171
|
+
let objectText = objectTextAt(text, start);
|
|
172
|
+
let repair;
|
|
173
|
+
if (!objectText) {
|
|
174
|
+
const closed = closeObjectAt(text, start);
|
|
175
|
+
if (!closed) {
|
|
176
|
+
if (!defect && contract.attempted(text.slice(start)))
|
|
177
|
+
defect = unclosedDefect(text.slice(start));
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
objectText = closed.text;
|
|
181
|
+
repair = closedReplyNote(closed.closers, contract.noun, closed.replaced);
|
|
182
|
+
}
|
|
183
|
+
// Agents echo their instructions. The shape template we sent is a valid,
|
|
184
|
+
// answer-shaped object full of placeholders, so it must never be read as
|
|
185
|
+
// an answer.
|
|
186
|
+
if (sent && sent.includes(collapse(objectText)))
|
|
187
|
+
continue;
|
|
188
|
+
try {
|
|
189
|
+
const parsed = JSON.parse(objectText);
|
|
190
|
+
if (contract.accept(parsed))
|
|
191
|
+
return { value: parsed, ...(repair ? { repair } : {}) };
|
|
192
|
+
}
|
|
193
|
+
catch (error) {
|
|
194
|
+
// A brace-balanced candidate can still be malformed when a closer is
|
|
195
|
+
// the wrong kind (`"}]}` ending a string array); a swapped tail is
|
|
196
|
+
// rewritten deterministically before the defect is reported.
|
|
197
|
+
const rewritten = closeObjectAt(text, start);
|
|
198
|
+
if (rewritten?.replaced !== undefined) {
|
|
199
|
+
try {
|
|
200
|
+
const parsed = JSON.parse(rewritten.text);
|
|
201
|
+
if (contract.accept(parsed))
|
|
202
|
+
return { value: parsed, repair: closedReplyNote(rewritten.closers, contract.noun, rewritten.replaced) };
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
// Fall through to report the original defect.
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// Prose and templates fail here too; only report a candidate that was
|
|
209
|
+
// clearly trying to be the answer.
|
|
210
|
+
if (!defect && contract.attempted(objectText)) {
|
|
211
|
+
defect = error instanceof Error ? error.message : String(error);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return defect ? { defect } : {};
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* The agent's own final reply, read out of its machine-readable stream:
|
|
219
|
+
* Claude's `result` event, Codex's completed `agent_message` items, and
|
|
220
|
+
* Gemini's assistant `message` events. Undefined when the output is not a
|
|
221
|
+
* stream, in which case the whole transcript is searched instead.
|
|
222
|
+
*/
|
|
223
|
+
export function agentReplyFromStream(raw, agent) {
|
|
224
|
+
const events = raw.split(/\r?\n/).flatMap((line) => {
|
|
225
|
+
try {
|
|
226
|
+
const value = JSON.parse(line);
|
|
227
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? [value] : [];
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
});
|
|
233
|
+
if (agent === 'claude') {
|
|
234
|
+
const results = events.flatMap((event) => event.type === 'result' && typeof event.result === 'string' ? [event.result] : []);
|
|
235
|
+
return results.at(-1);
|
|
236
|
+
}
|
|
237
|
+
if (agent === 'codex') {
|
|
238
|
+
const messages = events.flatMap((event) => {
|
|
239
|
+
const item = event.type === 'item.completed' && event.item && typeof event.item === 'object' ? event.item : undefined;
|
|
240
|
+
return item?.type === 'agent_message' && typeof item.text === 'string' ? [item.text] : [];
|
|
241
|
+
});
|
|
242
|
+
return messages.length > 0 ? messages.join('\n') : undefined;
|
|
243
|
+
}
|
|
244
|
+
const parts = events.flatMap((event) => event.type === 'message' && event.role === 'assistant' && typeof event.content === 'string' ? [event.content] : []);
|
|
245
|
+
return parts.length > 0 ? parts.join('') : undefined;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Read the object an agent was asked to return inside `<tag>…</tag>`. The
|
|
249
|
+
* agreed block wins outright when the agent honors it; otherwise the whole
|
|
250
|
+
* reply is searched, skipping anything that merely echoes the prompt.
|
|
251
|
+
* Returns undefined when nothing answer-shaped is found and no candidate was
|
|
252
|
+
* clearly trying to be the answer, so callers can fall back to another
|
|
253
|
+
* contract; throws when a candidate was found but is malformed.
|
|
254
|
+
*/
|
|
255
|
+
export function readTaggedJson(raw, agent, contract, prompt) {
|
|
256
|
+
const sent = prompt ? collapse(prompt) : undefined;
|
|
257
|
+
let candidate = raw.trim();
|
|
258
|
+
const reply = agentReplyFromStream(candidate, agent);
|
|
259
|
+
if (reply)
|
|
260
|
+
candidate = reply;
|
|
261
|
+
const pattern = new RegExp(`<${contract.tag}>\\s*([\\s\\S]*?)\\s*</${contract.tag}>`, 'gi');
|
|
262
|
+
const blocks = [...candidate.matchAll(pattern)].flatMap((match) => (match[1] ? [match[1]] : []));
|
|
263
|
+
let defect;
|
|
264
|
+
for (const text of [...blocks.reverse(), candidate]) {
|
|
265
|
+
const result = lastAcceptedIn(text, contract, sent);
|
|
266
|
+
if (result.value !== undefined)
|
|
267
|
+
return { value: result.value, repairs: result.repair ? [result.repair] : [] };
|
|
268
|
+
defect ??= result.defect;
|
|
269
|
+
}
|
|
270
|
+
if (defect) {
|
|
271
|
+
throw new DoxloopError(`The agent returned a malformed ${contract.noun} JSON object (${defect}). Open the full log, then retry.`);
|
|
272
|
+
}
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
const PLAN_CONTRACT = {
|
|
276
|
+
tag: 'doxloop-plan',
|
|
277
|
+
accept: looksLikeDocumentationPlan,
|
|
278
|
+
attempted: (text) => /"(?:pages|productProfile|capabilities)"\s*:/.test(text),
|
|
279
|
+
noun: 'documentation-plan',
|
|
280
|
+
};
|
|
281
|
+
export function readPlanOutput(raw, agent, prompt) {
|
|
282
|
+
let reply;
|
|
283
|
+
try {
|
|
284
|
+
reply = readTaggedJson(raw, agent, PLAN_CONTRACT, prompt);
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
const defect = error instanceof Error ? error.message.replace(/^The agent returned a malformed documentation-plan JSON object \((.*)\)\. Open the full log, then retry\.$/s, '$1') : String(error);
|
|
288
|
+
throw new DoxloopError(`The planning agent returned a malformed documentation-plan JSON object (${defect}). Open the full log, then retry the plan.`);
|
|
289
|
+
}
|
|
290
|
+
if (!reply) {
|
|
291
|
+
throw new DoxloopError('The planning agent did not return a valid documentation-plan JSON object. Open the full log, then retry the plan.');
|
|
292
|
+
}
|
|
293
|
+
return { plan: reply.value, repairs: reply.repairs.map((note) => note.replace(/^The documentation-plan reply/, "The planner's reply")) };
|
|
294
|
+
}
|
|
295
|
+
export function extractPlanOutput(raw, agent, prompt) {
|
|
296
|
+
return readPlanOutput(raw, agent, prompt).plan;
|
|
297
|
+
}
|
|
298
|
+
const PLAN_PATCH_KEYS = ['pages', 'removePageIds', 'capabilities', 'navigation', 'existingDocumentation', 'questions'];
|
|
299
|
+
const PLAN_PATCH_CONTRACT = {
|
|
300
|
+
tag: 'doxloop-plan-patch',
|
|
301
|
+
accept: (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
|
302
|
+
&& PLAN_PATCH_KEYS.some((key) => key in value),
|
|
303
|
+
attempted: (text) => /"(?:pages|removePageIds)"\s*:/.test(text),
|
|
304
|
+
noun: 'documentation-plan patch',
|
|
305
|
+
};
|
|
306
|
+
/**
|
|
307
|
+
* A corrective pass answers with the pages it changed inside
|
|
308
|
+
* `<doxloop-plan-patch>`. Undefined when the reply carries no patch at all
|
|
309
|
+
* (an agent that resent the whole plan is read with `readPlanOutput`).
|
|
310
|
+
*/
|
|
311
|
+
export function readPlanPatchOutput(raw, agent, prompt) {
|
|
312
|
+
const reply = readTaggedJson(raw, agent, PLAN_PATCH_CONTRACT, prompt);
|
|
313
|
+
return reply ? { ...reply, repairs: reply.repairs.map((note) => note.replace(/^The documentation-plan patch reply/, "The planner's corrective reply")) } : undefined;
|
|
314
|
+
}
|
|
315
|
+
//# sourceMappingURL=agent-reply.js.map
|
package/dist/agents.d.ts
CHANGED
|
@@ -1,4 +1,18 @@
|
|
|
1
|
+
import type { ChildProcess } from 'node:child_process';
|
|
1
2
|
import type { AgentName } from './types.js';
|
|
3
|
+
export declare const AGENT_CATALOG: readonly [{
|
|
4
|
+
readonly name: "codex";
|
|
5
|
+
readonly displayName: "Codex";
|
|
6
|
+
readonly packageName: "@openai/codex";
|
|
7
|
+
}, {
|
|
8
|
+
readonly name: "claude";
|
|
9
|
+
readonly displayName: "Claude Code";
|
|
10
|
+
readonly packageName: "@anthropic-ai/claude-code";
|
|
11
|
+
}, {
|
|
12
|
+
readonly name: "gemini";
|
|
13
|
+
readonly displayName: "Gemini";
|
|
14
|
+
readonly packageName: "@google/gemini-cli";
|
|
15
|
+
}];
|
|
2
16
|
export declare function parseAgent(value: string | undefined): AgentName | undefined;
|
|
3
17
|
export declare function installSkill(options: {
|
|
4
18
|
root: string;
|
|
@@ -16,15 +30,37 @@ export declare function detectAgents(): Promise<Array<{
|
|
|
16
30
|
name: AgentName;
|
|
17
31
|
executable: string;
|
|
18
32
|
}>>;
|
|
19
|
-
export declare function
|
|
33
|
+
export declare function installAgent(name: AgentName, options?: {
|
|
34
|
+
onOutput?: (output: string) => void;
|
|
35
|
+
onChild?: (child: ChildProcess) => void;
|
|
36
|
+
}): Promise<{
|
|
20
37
|
name: AgentName;
|
|
21
38
|
executable: string;
|
|
22
39
|
}>;
|
|
23
|
-
export declare function
|
|
40
|
+
export declare function chooseAgent(preferred?: AgentName): Promise<{
|
|
24
41
|
name: AgentName;
|
|
25
42
|
executable: string;
|
|
26
|
-
}
|
|
43
|
+
}>;
|
|
44
|
+
export interface AgentAuthentication {
|
|
27
45
|
status: 'authenticated' | 'unauthenticated' | 'unknown';
|
|
28
46
|
detail: string;
|
|
29
|
-
}
|
|
47
|
+
}
|
|
48
|
+
export declare function agentAuthenticationStatus(agent: {
|
|
49
|
+
name: AgentName;
|
|
50
|
+
executable: string;
|
|
51
|
+
}, options?: {
|
|
52
|
+
env?: NodeJS.ProcessEnv;
|
|
53
|
+
home?: string;
|
|
54
|
+
}): Promise<AgentAuthentication>;
|
|
55
|
+
/**
|
|
56
|
+
* Gemini CLI has no non-interactive status command, so its sign-in is read
|
|
57
|
+
* from the places it keeps credentials: an API key or Vertex AI project in
|
|
58
|
+
* the environment, or the OAuth token file a Google sign-in leaves behind.
|
|
59
|
+
* Only presence is checked; no credential value is read into memory for
|
|
60
|
+
* longer than the parse, and none is reported.
|
|
61
|
+
*/
|
|
62
|
+
export declare function geminiAuthenticationStatus(options?: {
|
|
63
|
+
env?: NodeJS.ProcessEnv;
|
|
64
|
+
home?: string;
|
|
65
|
+
}): Promise<AgentAuthentication>;
|
|
30
66
|
//# sourceMappingURL=agents.d.ts.map
|