@jameslovespancakes/pi-plus 1.0.11 → 1.0.12
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/README.md +44 -20
- package/config/skills/workflow-code-review-actions/SKILL.md +44 -0
- package/package.json +6 -3
- package/src/core/accounts/oauth-pool.ts +102 -0
- package/src/core/accounts/registry.ts +8 -25
- package/src/core/accounts/routing.ts +122 -0
- package/src/core/anthropic/client-identity.ts +9 -64
- package/src/core/anthropic/quota.ts +8 -53
- package/src/core/anthropic/routing.ts +40 -115
- package/src/core/anthropic/store.ts +7 -9
- package/src/core/catalog/quality.ts +33 -15
- package/src/core/codex/quota.ts +8 -2
- package/src/core/codex/store.ts +12 -9
- package/src/core/config.ts +4 -17
- package/src/core/store.ts +13 -12
- package/src/domains/models/provider-picker.ts +1 -1
- package/src/domains/setup/index.ts +23 -23
- package/src/domains/subscriptions/accounts-picker.ts +18 -37
- package/src/domains/subscriptions/accounts.ts +23 -29
- package/src/domains/subscriptions/footer.ts +48 -29
- package/src/domains/subscriptions/index.ts +11 -27
- package/src/domains/subscriptions/provider.ts +38 -103
- package/src/domains/subscriptions/providers/anthropic.ts +5 -17
- package/src/domains/subscriptions/providers/codex.ts +171 -4
- package/src/domains/subscriptions/providers/hosted.ts +18 -0
- package/src/domains/subscriptions/providers/oauth-pool.ts +273 -0
- package/src/domains/subscriptions/routing.ts +19 -11
- package/src/domains/workflows/LICENSE.md +21 -0
- package/src/domains/workflows/index.ts +836 -0
- package/src/domains/workflows/runtime/advisory-challenge.ts +75 -0
- package/src/domains/workflows/runtime/advisory-evidence.ts +90 -0
- package/src/domains/workflows/runtime/advisory-schema.ts +83 -0
- package/src/domains/workflows/runtime/agent-attempt.ts +147 -0
- package/src/domains/workflows/runtime/agent-limits.ts +43 -0
- package/src/domains/workflows/runtime/agent-replay.ts +399 -0
- package/src/domains/workflows/runtime/agent-retry.ts +116 -0
- package/src/domains/workflows/runtime/agent-runner-types.ts +96 -0
- package/src/domains/workflows/runtime/agent-runner.ts +203 -0
- package/src/domains/workflows/runtime/agent-session-identity.ts +256 -0
- package/src/domains/workflows/runtime/agent-session-providers.ts +50 -0
- package/src/domains/workflows/runtime/agent-session.ts +382 -0
- package/src/domains/workflows/runtime/agent-skills.ts +270 -0
- package/src/domains/workflows/runtime/agent-workspace.ts +103 -0
- package/src/domains/workflows/runtime/background-workflow-tool.ts +75 -0
- package/src/domains/workflows/runtime/background-workflows.ts +492 -0
- package/src/domains/workflows/runtime/budget.ts +53 -0
- package/src/domains/workflows/runtime/cancellation.ts +87 -0
- package/src/domains/workflows/runtime/command-completions.ts +36 -0
- package/src/domains/workflows/runtime/concurrency.ts +403 -0
- package/src/domains/workflows/runtime/debug.ts +3 -0
- package/src/domains/workflows/runtime/diff-capture.ts +81 -0
- package/src/domains/workflows/runtime/discovery.ts +137 -0
- package/src/domains/workflows/runtime/dynamax-shortcuts.ts +122 -0
- package/src/domains/workflows/runtime/dynamax.ts +330 -0
- package/src/domains/workflows/runtime/engine.ts +543 -0
- package/src/domains/workflows/runtime/filesystem-error.ts +4 -0
- package/src/domains/workflows/runtime/finalizers.ts +66 -0
- package/src/domains/workflows/runtime/identity-canonicalization.ts +138 -0
- package/src/domains/workflows/runtime/identity-fingerprint.ts +15 -0
- package/src/domains/workflows/runtime/inline-workflow.ts +403 -0
- package/src/domains/workflows/runtime/journal.ts +313 -0
- package/src/domains/workflows/runtime/model-profiles.ts +310 -0
- package/src/domains/workflows/runtime/options.ts +157 -0
- package/src/domains/workflows/runtime/perf.ts +146 -0
- package/src/domains/workflows/runtime/pi-compat.ts +32 -0
- package/src/domains/workflows/runtime/process-runner.ts +260 -0
- package/src/domains/workflows/runtime/progress-types.ts +59 -0
- package/src/domains/workflows/runtime/progress.ts +400 -0
- package/src/domains/workflows/runtime/provider-usage-limit.ts +191 -0
- package/src/domains/workflows/runtime/replay-path-identity.ts +48 -0
- package/src/domains/workflows/runtime/research-contract.ts +89 -0
- package/src/domains/workflows/runtime/research-evidence.ts +289 -0
- package/src/domains/workflows/runtime/resume-context.ts +751 -0
- package/src/domains/workflows/runtime/review/code-review-orchestration.ts +24 -0
- package/src/domains/workflows/runtime/review/github-pr-comments.ts +249 -0
- package/src/domains/workflows/runtime/review/patch-validation.ts +120 -0
- package/src/domains/workflows/runtime/review/review-actions.ts +180 -0
- package/src/domains/workflows/runtime/review/review-budget.ts +45 -0
- package/src/domains/workflows/runtime/review/review-fix-workflow.ts +153 -0
- package/src/domains/workflows/runtime/review/review-format.ts +133 -0
- package/src/domains/workflows/runtime/review/review-handoff.ts +38 -0
- package/src/domains/workflows/runtime/review/review-issues.ts +87 -0
- package/src/domains/workflows/runtime/review/review-report.ts +42 -0
- package/src/domains/workflows/runtime/review/review-results-flow.ts +52 -0
- package/src/domains/workflows/runtime/review/review-results-viewer.ts +312 -0
- package/src/domains/workflows/runtime/review/review-session-coordinator.ts +153 -0
- package/src/domains/workflows/runtime/review/review-snapshot.ts +294 -0
- package/src/domains/workflows/runtime/review-diff-target.ts +130 -0
- package/src/domains/workflows/runtime/session-identity.ts +6 -0
- package/src/domains/workflows/runtime/structured-output.ts +32 -0
- package/src/domains/workflows/runtime/tool-capabilities.ts +44 -0
- package/src/domains/workflows/runtime/tool-source-identity.ts +150 -0
- package/src/domains/workflows/runtime/tree-fingerprint.ts +404 -0
- package/src/domains/workflows/runtime/types.ts +255 -0
- package/src/domains/workflows/runtime/ui/display-text.ts +13 -0
- package/src/domains/workflows/runtime/ui/dynamax-editor-decoration.ts +220 -0
- package/src/domains/workflows/runtime/ui/workflow-format.ts +165 -0
- package/src/domains/workflows/runtime/ui/workflow-inspector.ts +398 -0
- package/src/domains/workflows/runtime/ui/workflow-result-renderer.ts +171 -0
- package/src/domains/workflows/runtime/ui/workflow-viewer-layout.ts +51 -0
- package/src/domains/workflows/runtime/ui/workflow-widget.ts +78 -0
- package/src/domains/workflows/runtime/unknown-error.ts +8 -0
- package/src/domains/workflows/runtime/usage.ts +341 -0
- package/src/domains/workflows/runtime/workflow-advisory-utils.ts +245 -0
- package/src/domains/workflows/runtime/workflow-execution.ts +121 -0
- package/src/domains/workflows/runtime/workflow-module.ts +75 -0
- package/src/domains/workflows/runtime/workflow-run-background.ts +72 -0
- package/src/domains/workflows/runtime/workflow-run-controller.ts +342 -0
- package/src/domains/workflows/runtime/workflow-run-history.ts +162 -0
- package/src/domains/workflows/runtime/workflow-run-record.ts +635 -0
- package/src/domains/workflows/runtime/workflow-run-store.ts +176 -0
- package/src/domains/workflows/runtime/workflow-usage-limit-scheduler.ts +80 -0
- package/src/domains/workflows/runtime/workflows.ts +40 -0
- package/src/domains/workflows/runtime/worktree.ts +581 -0
- package/src/domains/workflows/workflows/code-review.ts +232 -0
- package/src/domains/workflows/workflows/diagnose.ts +154 -0
- package/src/domains/workflows/workflows/perf-review.ts +149 -0
- package/src/domains/workflows/workflows/refactor-scout.ts +143 -0
- package/src/domains/workflows/workflows/research.ts +169 -0
- package/src/services/usage-service.ts +6 -30
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { challengeFindings, parseChallengeArgs } from "../runtime/advisory-challenge.ts";
|
|
2
|
+
import { Type } from "typebox";
|
|
3
|
+
import {
|
|
4
|
+
type AdvisoryVerified,
|
|
5
|
+
type AdvisoryLens,
|
|
6
|
+
synthesizeAdvisoryReport,
|
|
7
|
+
finishAdvisoryReport,
|
|
8
|
+
emptyAdvisoryReport,
|
|
9
|
+
formatEvidence,
|
|
10
|
+
formatLocation,
|
|
11
|
+
publishVerifiedKeptProgress,
|
|
12
|
+
runLensVerificationPipeline,
|
|
13
|
+
DEFAULT_ADVISORY_TOOL_HINTS,
|
|
14
|
+
DEFAULT_ADVISORY_TOOLS,
|
|
15
|
+
} from "../runtime/workflow-advisory-utils.ts";
|
|
16
|
+
import type { WorkflowApi, WorkflowMeta, WorkflowRunStats } from "../runtime/types.ts";
|
|
17
|
+
|
|
18
|
+
export const meta: WorkflowMeta = {
|
|
19
|
+
name: "refactor-scout",
|
|
20
|
+
description: "Advisory-only refactor scout: scope → per-lens find → independent verify → synthesize safe refactor opportunities.",
|
|
21
|
+
phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Challenge" }, { title: "Synthesize" }],
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const ScopeSchema = Type.Object({
|
|
25
|
+
target: Type.String({ description: "Verbatim target path, module, or focus area being scouted." }),
|
|
26
|
+
files: Type.Array(Type.String(), { description: "Repository-relative files in scope." }),
|
|
27
|
+
summary: Type.String({ description: "One-paragraph summary of the scoped code." }),
|
|
28
|
+
conventions: Type.Optional(Type.String({ description: "Relevant project conventions from AGENTS.md / docs." })),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const REFACTOR_LENSES: AdvisoryLens[] = [
|
|
32
|
+
{ label: "duplication", category: "duplication", text: "Repeated logic, copy-pasted structures, or near-duplicate flows that could share one clearer implementation." },
|
|
33
|
+
{ label: "complexity", category: "complexity", text: "Oversized functions, tangled control flow, or abstractions that make local reasoning harder than necessary." },
|
|
34
|
+
{ label: "type-safety", category: "type-safety", text: "Weak typing, avoidable casts, unchecked shapes, or places stronger types would prevent mistakes." },
|
|
35
|
+
{ label: "boundaries", category: "boundary", text: "Leaky module boundaries, misplaced responsibilities, or imports that couple unrelated layers." },
|
|
36
|
+
{ label: "dead-code", category: "dead-code", text: "Unused, obsolete, or redundant code paths that can likely be removed safely." },
|
|
37
|
+
{ label: "conventions", category: "conventions", text: "Departures from project conventions, naming, dependency rules, or local idioms." },
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
const PER_LENS = 5;
|
|
41
|
+
|
|
42
|
+
export default async function run(api: WorkflowApi): Promise<unknown> {
|
|
43
|
+
const { agent, phase, log, progress, args } = api;
|
|
44
|
+
const challengeConfig = parseChallengeArgs(args);
|
|
45
|
+
const target = challengeConfig.args.trim() || ".";
|
|
46
|
+
let fileCount = 0;
|
|
47
|
+
let rawCandidateCount = 0;
|
|
48
|
+
let droppedCandidateCount = 0;
|
|
49
|
+
let refutedCandidateCount = 0;
|
|
50
|
+
const makeStats = (verified: number, kept: number): WorkflowRunStats => ({
|
|
51
|
+
files: fileCount,
|
|
52
|
+
candidates: rawCandidateCount,
|
|
53
|
+
verified,
|
|
54
|
+
kept,
|
|
55
|
+
dropped: droppedCandidateCount,
|
|
56
|
+
refuted: refutedCandidateCount,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
phase("Scope");
|
|
60
|
+
const scope = await agent(
|
|
61
|
+
"Establish the scope for an advisory-only refactor scout. Do not edit files.\n" +
|
|
62
|
+
`Target / focus (verbatim): ${target}\n\n` +
|
|
63
|
+
"Inspect repository structure, the target path or module, and relevant AGENTS.md / project docs conventions. " +
|
|
64
|
+
"Return the concrete files that should be considered, a short summary, and any conventions that affect refactor advice. " +
|
|
65
|
+
`This workflow will fan out across ${REFACTOR_LENSES.length} lenses with up to ${PER_LENS} candidates per lens. Structured output only.`,
|
|
66
|
+
{ phase: "Scope", label: "scope", tools: DEFAULT_ADVISORY_TOOLS, toolHints: DEFAULT_ADVISORY_TOOL_HINTS, profile: "medium", schema: ScopeSchema },
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
if (!scope || scope.files.length === 0) {
|
|
70
|
+
return finishAdvisoryReport(emptyAdvisoryReport(
|
|
71
|
+
"No files were identified for refactor scouting.",
|
|
72
|
+
["Provide a target path, module, or subsystem to scout for refactor opportunities."],
|
|
73
|
+
makeStats(0, 0),
|
|
74
|
+
), []);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
fileCount = scope.files.length;
|
|
78
|
+
progress({ type: "counter", key: "files", label: "files", value: fileCount });
|
|
79
|
+
progress({ type: "summary", key: "files", value: scope.files.join(", ") });
|
|
80
|
+
log(`${scope.files.length} files scoped for refactor scouting`);
|
|
81
|
+
|
|
82
|
+
const scopeBlock =
|
|
83
|
+
`## Target\n${scope.target}\n\n## Files in scope\n${scope.files.map((file) => `- ${file}`).join("\n")}\n\n` +
|
|
84
|
+
`## Summary\n${scope.summary}\n\n## Conventions\n${scope.conventions ?? "(none noted)"}\n` +
|
|
85
|
+
(args.trim() ? `\n## User instructions (verbatim)\n${args.trim()}\n` : "");
|
|
86
|
+
|
|
87
|
+
const pipelineResult = await runLensVerificationPipeline({
|
|
88
|
+
api,
|
|
89
|
+
lenses: REFACTOR_LENSES,
|
|
90
|
+
perLens: PER_LENS,
|
|
91
|
+
finderPrompt: (lens) =>
|
|
92
|
+
`## Refactor-scout finder — ${lens.label}\n\n${scopeBlock}\n` +
|
|
93
|
+
"This workflow is advisory-only: do not edit files and do not propose broad rewrites.\n" +
|
|
94
|
+
`Scout through ONLY this lens:\n${lens.text}\n\n` +
|
|
95
|
+
`Surface up to ${PER_LENS} candidates. Use category exactly "${lens.category}". ` +
|
|
96
|
+
"Each candidate must include a one-line summary, locations, impact on maintainability or future correctness, and an optional safe first recommendation. " +
|
|
97
|
+
"Only include opportunities where a small, reviewable first step is plausible. Structured output only.",
|
|
98
|
+
verifierPrompt: (candidate) =>
|
|
99
|
+
`## Refactor-scout verifier\n\n${scopeBlock}\n## Candidate\n` +
|
|
100
|
+
`Location: ${formatLocation(candidate)}\nCategory: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n` +
|
|
101
|
+
`Recommendation: ${candidate.recommendation ?? "(none supplied)"}\n\n` +
|
|
102
|
+
"Read the relevant files and return CONFIRMED, PLAUSIBLE, NOT_SUBSTANTIATED, or REFUTED. " +
|
|
103
|
+
"Default toward REFUTED if the opportunity is generic, too broad, not evidenced by code, or lacks a safe first step. " +
|
|
104
|
+
"Evidence must quote or cite code. Structured output only.",
|
|
105
|
+
});
|
|
106
|
+
rawCandidateCount += pipelineResult.rawCandidates;
|
|
107
|
+
droppedCandidateCount += pipelineResult.dropped;
|
|
108
|
+
refutedCandidateCount += pipelineResult.refuted;
|
|
109
|
+
const { coverage } = pipelineResult;
|
|
110
|
+
const verified = await challengeFindings(api, pipelineResult.verified, scopeBlock, challengeConfig.options, coverage);
|
|
111
|
+
const surviving = verified.filter((finding) => finding.verdict !== "REFUTED");
|
|
112
|
+
const stats = makeStats(verified.length, surviving.length);
|
|
113
|
+
publishVerifiedKeptProgress({ progress, log }, verified.length, surviving.length);
|
|
114
|
+
|
|
115
|
+
if (surviving.length === 0) {
|
|
116
|
+
return finishAdvisoryReport(emptyAdvisoryReport("No refactor opportunities survived verification.", ["Leave the scoped code unchanged unless a human reviewer has additional context."], stats), coverage, verified);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const ranked = [...surviving].sort((a, b) => rank(a) - rank(b));
|
|
120
|
+
const block = ranked
|
|
121
|
+
.map(
|
|
122
|
+
(finding, index) =>
|
|
123
|
+
`### [${index}] IDs: ${finding.sourceCandidateIds.join(", ")} ${formatLocation(finding)} (${finding.verdict}, ${finding.category})\n` +
|
|
124
|
+
`${finding.summary}\nImpact: ${finding.impact}\nEvidence: ${formatEvidence(finding.evidence)}\nSafe first step: ${finding.recommendation ?? "(none supplied)"}`,
|
|
125
|
+
)
|
|
126
|
+
.join("\n\n");
|
|
127
|
+
|
|
128
|
+
const resolved = await synthesizeAdvisoryReport(api,
|
|
129
|
+
`## Synthesis: final refactor-scout report\n\n${ranked.length} opportunities survived independent verification.\n\n${block}\n\n` +
|
|
130
|
+
"Merge findings with the same root cause and rank highest leverage / lowest risk first. " +
|
|
131
|
+
"Select findings by ID. " +
|
|
132
|
+
"Severity is maintenance or future-correctness impact. " +
|
|
133
|
+
"Recommendations must be safe first refactor steps, not rewrites. Include concrete nextSteps for the host developer. Structured output only.",
|
|
134
|
+
ranked, coverage,
|
|
135
|
+
);
|
|
136
|
+
return finishAdvisoryReport({ ...resolved, stats: { ...stats, kept: resolved.findings.length } }, coverage, verified);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function rank(finding: AdvisoryVerified): number {
|
|
140
|
+
const verdictRank = finding.verdict === "CONFIRMED" ? 0 : 1;
|
|
141
|
+
const categoryRank = finding.category === "dead-code" || finding.category === "conventions" ? 2 : 0;
|
|
142
|
+
return verdictRank + categoryRank;
|
|
143
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { compactResults } from "../runtime/concurrency.ts";
|
|
2
|
+
import {
|
|
3
|
+
MAX_RESEARCH_LANES,
|
|
4
|
+
ResearchLaneResultSchema,
|
|
5
|
+
ResearchPlanSchema,
|
|
6
|
+
ResearchReportSchema,
|
|
7
|
+
ResearchVerificationSchema,
|
|
8
|
+
type ResearchClaimCandidate,
|
|
9
|
+
type ResearchReport,
|
|
10
|
+
} from "../runtime/research-contract.ts";
|
|
11
|
+
import {
|
|
12
|
+
buildClaimCandidates,
|
|
13
|
+
fallbackResearchReport,
|
|
14
|
+
normalizeResearchLanes,
|
|
15
|
+
sanitizeLaneResults,
|
|
16
|
+
sanitizeResearchReport,
|
|
17
|
+
sanitizeVerification,
|
|
18
|
+
unavailableResearchReport,
|
|
19
|
+
unavailableVerification,
|
|
20
|
+
} from "../runtime/research-evidence.ts";
|
|
21
|
+
import { WorkflowToolHintUnavailableError } from "../runtime/tool-capabilities.ts";
|
|
22
|
+
import type { WorkflowApi, WorkflowMeta } from "../runtime/types.ts";
|
|
23
|
+
|
|
24
|
+
export const meta: WorkflowMeta = {
|
|
25
|
+
name: "research",
|
|
26
|
+
description: "Source-grounded external research: decompose → gather direct-page evidence → independently verify claims → cited synthesis.",
|
|
27
|
+
phases: [{ title: "Plan" }, { title: "Gather" }, { title: "Verify" }, { title: "Synthesize" }],
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const EXTERNAL_TOOLS: string[] = [];
|
|
31
|
+
const EXTERNAL_TOOL_HINTS = ["external-search"] as const;
|
|
32
|
+
|
|
33
|
+
export default async function run(api: WorkflowApi): Promise<ResearchReport> {
|
|
34
|
+
const { agent, parallel, phase, log, progress, args } = api;
|
|
35
|
+
const question = args.trim();
|
|
36
|
+
if (!question) return unavailableResearchReport("empty-question");
|
|
37
|
+
|
|
38
|
+
phase("Plan");
|
|
39
|
+
let plan;
|
|
40
|
+
try {
|
|
41
|
+
plan = await agent(
|
|
42
|
+
`Plan bounded, source-grounded research for the user's question and any scope constraints embedded in it.\n\n` +
|
|
43
|
+
`Question and constraints (verbatim):\n${question}\n\n` +
|
|
44
|
+
`Create at most ${MAX_RESEARCH_LANES} non-overlapping lanes. Each lane needs a stable short id, title, objective, and 1-4 concrete search queries. ` +
|
|
45
|
+
"Separate primary-source discovery, current status, counterevidence, or jurisdiction/timeframe only when relevant. " +
|
|
46
|
+
"Do not claim exhaustive coverage. Structured output only.",
|
|
47
|
+
{
|
|
48
|
+
phase: "Plan",
|
|
49
|
+
label: "plan",
|
|
50
|
+
tools: EXTERNAL_TOOLS,
|
|
51
|
+
toolHints: EXTERNAL_TOOL_HINTS,
|
|
52
|
+
requireToolHints: true,
|
|
53
|
+
profile: "medium",
|
|
54
|
+
resume: "off",
|
|
55
|
+
schema: ResearchPlanSchema,
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error instanceof WorkflowToolHintUnavailableError) return unavailableResearchReport("missing-capability");
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!plan) return unavailableResearchReport("no-evidence");
|
|
64
|
+
const lanes = normalizeResearchLanes(plan);
|
|
65
|
+
if (lanes.length === 0) return unavailableResearchReport("no-evidence");
|
|
66
|
+
progress({ type: "counter", key: "research.lanes", label: "research lanes", value: lanes.length });
|
|
67
|
+
progress({ type: "summary", key: "research.question", value: question });
|
|
68
|
+
|
|
69
|
+
phase("Gather");
|
|
70
|
+
const gathered = compactResults(
|
|
71
|
+
await parallel(
|
|
72
|
+
lanes.map((lane) => async () => {
|
|
73
|
+
const result = await agent(
|
|
74
|
+
`Research one bounded lane using only installed external web-search, browsing, or URL-extraction tools.\n\n` +
|
|
75
|
+
`Question: ${question}\n` +
|
|
76
|
+
`Scope constraints: ${plan.scopeConstraints.join("; ") || "(none)"}\n` +
|
|
77
|
+
`Lane id: ${lane.id}\nLane: ${lane.title}\nObjective: ${lane.objective}\n` +
|
|
78
|
+
`Queries:\n${lane.queries.map((query) => `- ${query}`).join("\n")}\n\n` +
|
|
79
|
+
"Open the supporting pages instead of citing a search-results page. Prefer primary and authoritative sources; use independent sources when useful. " +
|
|
80
|
+
"Return concrete claims with importance, whether each page supports or conflicts with the claim, a short passage or precise paraphrase, and the exact page title and URL. " +
|
|
81
|
+
"State evidence gaps. Never invent a URL or claim that the opened page does not support. Structured output only.",
|
|
82
|
+
{
|
|
83
|
+
phase: "Gather",
|
|
84
|
+
label: `gather:${lane.id}`,
|
|
85
|
+
tools: EXTERNAL_TOOLS,
|
|
86
|
+
toolHints: EXTERNAL_TOOL_HINTS,
|
|
87
|
+
requireToolHints: true,
|
|
88
|
+
profile: "small",
|
|
89
|
+
resume: "off",
|
|
90
|
+
schema: ResearchLaneResultSchema,
|
|
91
|
+
},
|
|
92
|
+
);
|
|
93
|
+
if (!result) return null;
|
|
94
|
+
progress({ type: "counter_delta", key: "research.evidence", label: "evidence items", delta: result.evidence.length });
|
|
95
|
+
progress({
|
|
96
|
+
type: "lane_item",
|
|
97
|
+
lane: "Research lanes",
|
|
98
|
+
title: lane.title,
|
|
99
|
+
subtitle: `${result.evidence.length} evidence item(s)`,
|
|
100
|
+
status: result.evidence.length > 0 ? "success" : "warning",
|
|
101
|
+
details: result.gaps.join("; ") || lane.objective,
|
|
102
|
+
});
|
|
103
|
+
return result;
|
|
104
|
+
}),
|
|
105
|
+
),
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
const laneResults = sanitizeLaneResults(gathered);
|
|
109
|
+
const candidates = buildClaimCandidates(laneResults);
|
|
110
|
+
if (candidates.length === 0) return unavailableResearchReport("no-evidence");
|
|
111
|
+
progress({ type: "counter", key: "research.claims", label: "claims to verify", value: candidates.length });
|
|
112
|
+
log(`${candidates.length} bounded claim(s) selected for independent verification`);
|
|
113
|
+
|
|
114
|
+
phase("Verify");
|
|
115
|
+
const verificationResults = await parallel(
|
|
116
|
+
candidates.map((candidate, index) => async () => {
|
|
117
|
+
const result = await agent(verificationPrompt(question, candidate), {
|
|
118
|
+
phase: "Verify",
|
|
119
|
+
label: `verify:${index + 1}`,
|
|
120
|
+
tools: EXTERNAL_TOOLS,
|
|
121
|
+
toolHints: EXTERNAL_TOOL_HINTS,
|
|
122
|
+
requireToolHints: true,
|
|
123
|
+
profile: "medium",
|
|
124
|
+
resume: "off",
|
|
125
|
+
schema: ResearchVerificationSchema,
|
|
126
|
+
});
|
|
127
|
+
return result ? sanitizeVerification(result, candidate) : null;
|
|
128
|
+
}),
|
|
129
|
+
);
|
|
130
|
+
const verifications = verificationResults.map((result, index) => result ?? unavailableVerification(candidates[index]!));
|
|
131
|
+
for (const verification of verifications) {
|
|
132
|
+
progress({ type: "counter_delta", key: `research.${verification.verdict.toLowerCase()}`, label: verification.verdict.toLowerCase(), delta: 1 });
|
|
133
|
+
}
|
|
134
|
+
const synthesisInputs = verifications.filter((verification) => verification.verdict !== "REJECTED");
|
|
135
|
+
|
|
136
|
+
phase("Synthesize");
|
|
137
|
+
const synthesis = await agent(
|
|
138
|
+
`Answer the research question using only the independently verified handoff below.\n\n` +
|
|
139
|
+
`Question: ${question}\nScope constraints: ${plan.scopeConstraints.join("; ") || "(none)"}\n\n` +
|
|
140
|
+
`Verified claims JSON:\n${JSON.stringify(synthesisInputs)}\n\n` +
|
|
141
|
+
"Keep SUPPORTED claims, CONFLICTED evidence, UNCERTAIN claims, and model INFERENCE in their separate fields. Exclude REJECTED claims. " +
|
|
142
|
+
"Copy each verified claim string exactly so its citations remain bound to that claim during validation. " +
|
|
143
|
+
"Every supported or conflicting claim must cite exact title/URL objects from its verification; do not add URLs, cite search-results pages, or turn inference into fact. " +
|
|
144
|
+
"Answer concisely, disclose limited coverage, and provide useful next steps. Structured output only.",
|
|
145
|
+
{
|
|
146
|
+
phase: "Synthesize",
|
|
147
|
+
label: "synthesize",
|
|
148
|
+
tools: [],
|
|
149
|
+
profile: "medium",
|
|
150
|
+
resume: "off",
|
|
151
|
+
schema: ResearchReportSchema,
|
|
152
|
+
},
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
return sanitizeResearchReport(
|
|
156
|
+
synthesis ?? fallbackResearchReport(synthesisInputs, "The model did not return a structured synthesis."),
|
|
157
|
+
synthesisInputs,
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function verificationPrompt(question: string, candidate: ResearchClaimCandidate): string {
|
|
162
|
+
return `Independently verify one important research claim using installed external web-search, browsing, or URL-extraction tools.\n\n` +
|
|
163
|
+
`Question: ${question}\nClaim: ${candidate.claim}\nImportance: ${candidate.importance}\n\n` +
|
|
164
|
+
`Gather-stage evidence (context only; do not treat it as verified):\n${JSON.stringify(candidate.evidence)}\n\n` +
|
|
165
|
+
"Search independently and open direct supporting pages. Prefer primary/authoritative sources and actively look for credible counterevidence. " +
|
|
166
|
+
"Return SUPPORTED only when direct pages substantiate the claim, CONFLICTED when credible sources disagree, UNCERTAIN when evidence is insufficient, " +
|
|
167
|
+
"INFERENCE when the conclusion is reasoned rather than directly stated, or REJECTED when evidence refutes it. " +
|
|
168
|
+
"Include exact page titles and direct HTTP(S) URLs, never search-results URLs. Structured output only.";
|
|
169
|
+
}
|
|
@@ -2,15 +2,7 @@ import { agentPath, readJson, writeJson } from "../core/store.ts";
|
|
|
2
2
|
import { isClaudeAccount, type UsageRow } from "../core/quota/pool.ts";
|
|
3
3
|
import { fetchAll } from "../core/quota/usage-source.ts";
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* The single owner of subscription usage state.
|
|
7
|
-
*
|
|
8
|
-
* Previously the only poller lived inside the footer extension and was gated on
|
|
9
|
-
* `ctx.hasUI`, so `list_models` silently read empty rows whenever the footer was
|
|
10
|
-
* hidden or the session was headless (including every workflow subagent).
|
|
11
|
-
* Consumers now call `ensureFresh()` for on-demand data and `subscribe()` for
|
|
12
|
-
* push updates; only one poll is ever in flight regardless of consumer count.
|
|
13
|
-
*/
|
|
5
|
+
/** Shared subscription-usage cache and poller. */
|
|
14
6
|
|
|
15
7
|
export const REFRESH_MS = 5 * 60 * 1000;
|
|
16
8
|
const MIN_INTERVAL_MS = 90_000;
|
|
@@ -24,11 +16,7 @@ export interface UsageState {
|
|
|
24
16
|
loading: boolean;
|
|
25
17
|
accounts: number;
|
|
26
18
|
codexPlan?: string;
|
|
27
|
-
/**
|
|
28
|
-
* Last time each account group's quota was observed to drop. This is the only
|
|
29
|
-
* available proxy for "recently used": providers expose remaining quota but
|
|
30
|
-
* never report which account served a request.
|
|
31
|
-
*/
|
|
19
|
+
/** Last observed quota drop for each account group. */
|
|
32
20
|
lastUsedAt?: Record<string, number>;
|
|
33
21
|
}
|
|
34
22
|
|
|
@@ -77,10 +65,7 @@ function emit(): void {
|
|
|
77
65
|
}
|
|
78
66
|
}
|
|
79
67
|
|
|
80
|
-
/**
|
|
81
|
-
* Stamps an account as recently used when its headline window falls. A rise
|
|
82
|
-
* (quota reset) or an unchanged figure is not a usage signal.
|
|
83
|
-
*/
|
|
68
|
+
/** Records use when an account's headline quota falls. */
|
|
84
69
|
function recordUsageDrops(fresh: UsageRow[]): void {
|
|
85
70
|
const previous = new Map(
|
|
86
71
|
state.rows.filter((row) => row.label === "5h").map((row) => [row.group, row.remaining]),
|
|
@@ -98,10 +83,7 @@ export function usageState(): UsageState {
|
|
|
98
83
|
return state;
|
|
99
84
|
}
|
|
100
85
|
|
|
101
|
-
/**
|
|
102
|
-
* Account groups ordered by most recent observed use, capped at `limit`.
|
|
103
|
-
* Accounts never seen in use fall back to alphabetical, so the list is stable.
|
|
104
|
-
*/
|
|
86
|
+
/** Returns recently used account groups with stable fallback ordering. */
|
|
105
87
|
export function recentAccounts(limit: number): string[] {
|
|
106
88
|
const stamps = state.lastUsedAt ?? {};
|
|
107
89
|
const groups = [...new Set(state.rows.filter(isClaudeAccount).map((row) => row.group))];
|
|
@@ -116,10 +98,7 @@ export function subscribe(listener: () => void): () => void {
|
|
|
116
98
|
return () => listeners.delete(listener);
|
|
117
99
|
}
|
|
118
100
|
|
|
119
|
-
/**
|
|
120
|
-
* The endpoints are rate limited, so results are cached and refreshes are
|
|
121
|
-
* throttled. Failures keep the previous figures on screen.
|
|
122
|
-
*/
|
|
101
|
+
/** Refreshes cached usage without overlapping requests. */
|
|
123
102
|
export async function refreshUsage(ctx: any, force = false): Promise<void> {
|
|
124
103
|
if (inFlight) return inFlight;
|
|
125
104
|
const now = Date.now();
|
|
@@ -160,10 +139,7 @@ export async function refreshUsage(ctx: any, force = false): Promise<void> {
|
|
|
160
139
|
return inFlight;
|
|
161
140
|
}
|
|
162
141
|
|
|
163
|
-
/**
|
|
164
|
-
* Guarantees usable data for a caller that does not own the poll loop.
|
|
165
|
-
* This is what makes `list_models` correct in headless sessions.
|
|
166
|
-
*/
|
|
142
|
+
/** Refreshes stale data for UI and headless callers. */
|
|
167
143
|
export async function ensureFresh(ctx: any): Promise<UsageState> {
|
|
168
144
|
const stale = !state.updatedAt || Date.now() - state.updatedAt > REFRESH_MS;
|
|
169
145
|
if (stale) await refreshUsage(ctx);
|