@agent-finops/core 0.8.1 → 0.9.1
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 +5 -3
- package/dist/actionPlanner.d.ts +140 -0
- package/dist/actionPlanner.js +938 -0
- package/dist/actionVerification.d.ts +1240 -0
- package/dist/actionVerification.js +1028 -0
- package/dist/activitySnapshot.d.ts +142 -50
- package/dist/activitySnapshot.js +145 -6
- package/dist/activitySnapshotCache.d.ts +8 -1
- package/dist/activitySnapshotCache.js +103 -7
- package/dist/agentDraftToken.d.ts +80 -0
- package/dist/agentDraftToken.js +188 -0
- package/dist/agentEconomicsReceipt.d.ts +74 -74
- package/dist/agentLoopContract.d.ts +27 -0
- package/dist/agentLoopContract.js +36 -0
- package/dist/glance.d.ts +27 -1
- package/dist/glance.js +151 -12
- package/dist/guidedAnswer.d.ts +51 -0
- package/dist/guidedAnswer.js +352 -0
- package/dist/index.d.ts +14 -2
- package/dist/index.js +13 -1
- package/dist/localAgentFormats/gemini.js +2 -2
- package/dist/localAgentFormats/registry.js +6 -2
- package/dist/localAgentFormats/runtimeRegistry.js +5 -2
- package/dist/localAgentFormats/types.d.ts +2 -1
- package/dist/localAgentLogs.d.ts +362 -3
- package/dist/localAgentLogs.js +1964 -165
- package/dist/modelPricing.d.ts +1 -1
- package/dist/modelPricing.js +1 -1
- package/dist/projectEconomics.d.ts +617 -0
- package/dist/projectEconomics.js +620 -0
- package/dist/projectEconomicsBuilder.d.ts +89 -0
- package/dist/projectEconomicsBuilder.js +473 -0
- package/dist/projectIndexStore.d.ts +545 -0
- package/dist/projectIndexStore.js +606 -0
- package/dist/providerConnectors.d.ts +161 -1
- package/dist/providerConnectors.js +406 -11
- package/dist/qualitativeIndexCache.d.ts +494 -0
- package/dist/qualitativeIndexCache.js +930 -0
- package/dist/resultCard.d.ts +350 -0
- package/dist/resultCard.js +604 -0
- package/dist/runtimeCommands.d.ts +36 -0
- package/dist/runtimeCommands.js +50 -0
- package/dist/scanGuard.d.ts +3 -1
- package/dist/scanGuard.js +164 -4
- package/dist/schema.d.ts +33 -31
- package/dist/schema.js +9 -1
- package/dist/sessionVitals.d.ts +145 -0
- package/dist/sessionVitals.js +521 -0
- package/dist/toolInvocations.d.ts +40 -1
- package/dist/toolInvocations.js +101 -20
- package/package.json +1 -1
package/dist/glance.js
CHANGED
|
@@ -3,6 +3,7 @@ import { canPriceTokenUsageAtScope, estimateTokenCostUsd, PRICING_TABLE_AS_OF }
|
|
|
3
3
|
import { subscriptionPlans } from "./planMath.js";
|
|
4
4
|
import { buildContextHealth } from "./contextHealth.js";
|
|
5
5
|
import { localAgentFormatDescriptors, localAgentFormatSupports } from "./localAgentFormats/registry.js";
|
|
6
|
+
import { aibillImproveCommandV0 } from "./runtimeCommands.js";
|
|
6
7
|
const HOUR_MS = 60 * 60 * 1_000;
|
|
7
8
|
const DAY_MS = 24 * HOUR_MS;
|
|
8
9
|
/**
|
|
@@ -55,17 +56,37 @@ export function buildUsageGlance(calls, options = {}) {
|
|
|
55
56
|
const focusSessions = latest?.project && !isGenericProject(latest.project)
|
|
56
57
|
? windowSessions.filter((session) => session.project === latest.project)
|
|
57
58
|
: windowSessions;
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
});
|
|
59
|
+
const qualitativeCoverage = options.qualitativeCoverage ?? {
|
|
60
|
+
status: "complete",
|
|
61
|
+
selectedFiles: options.filesParsed ?? 0,
|
|
62
|
+
readCompletely: options.filesParsed ?? 0,
|
|
63
|
+
skippedForBudget: 0
|
|
64
|
+
};
|
|
65
|
+
const qualitativeComplete = qualitativeCoverage.status === "complete";
|
|
66
|
+
const focus = qualitativeComplete
|
|
67
|
+
? buildMainFocus(focusSessions, focusWindowDays, now)
|
|
68
|
+
: null;
|
|
69
|
+
const baseSessionHealth = suppliedContextHealth ?? buildContextHealth({ calls: safeCalls, now });
|
|
70
|
+
const sessionHealth = {
|
|
71
|
+
...baseSessionHealth,
|
|
72
|
+
qualitativeCoverage
|
|
73
|
+
};
|
|
74
|
+
const anomaly = qualitativeComplete ? anomalyFromContextHealth(sessionHealth) : null;
|
|
75
|
+
const primaryAction = qualitativeComplete
|
|
76
|
+
? buildPrimaryAction({
|
|
77
|
+
currentSession,
|
|
78
|
+
focus,
|
|
79
|
+
limits,
|
|
80
|
+
sessionHealth,
|
|
81
|
+
generatedAt: now.toISOString(),
|
|
82
|
+
filesParsed: options.filesParsed ?? 0
|
|
83
|
+
})
|
|
84
|
+
: buildCoverageLimitedPrimaryAction({
|
|
85
|
+
currentSession,
|
|
86
|
+
sessionHealth,
|
|
87
|
+
coverage: qualitativeCoverage
|
|
88
|
+
});
|
|
89
|
+
const tokenExperiment = sanitizeActionVerificationProjection(options.actionVerificationProjection);
|
|
69
90
|
const detectedAgents = (options.detectedAgents ?? uniqueAgents(safeCalls))
|
|
70
91
|
.filter((agent) => localAgentFormatSupports(agent, "glance"));
|
|
71
92
|
const agentsWithCurrentLimits = new Set(limits.filter((limit) => limit.freshness === "current").map((limit) => limit.agent));
|
|
@@ -81,14 +102,19 @@ export function buildUsageGlance(calls, options = {}) {
|
|
|
81
102
|
"A five-hour percentage is current for at most five hours after observation; a weekly percentage is current for at most 24 hours. Older transcript evidence is labeled stale and never drives an action.",
|
|
82
103
|
"Main focus is a local summary of observed human prompts and tool activity, not elapsed time or spend; raw prompts are not returned.",
|
|
83
104
|
"The primary action combines Context Health, Main focus, and reported runway locally. It only provides a copyable handoff prompt and never runs an agent automatically.",
|
|
105
|
+
"A token-test percentage compares matched local session cohorts guarded by explicit quality evidence; it is not certified savings, verified outcome ROI, or a provider bill.",
|
|
84
106
|
"Claude Code transcripts do not report plan headroom. Missing limits remain unavailable instead of being inferred.",
|
|
85
|
-
"Cursor and GitHub Copilot require their provider connections because their local chat stores are not treated as authoritative billing transcripts."
|
|
107
|
+
"Cursor and GitHub Copilot require their provider connections because their local chat stores are not treated as authoritative billing transcripts.",
|
|
108
|
+
...(qualitativeComplete ? [] : [
|
|
109
|
+
"Main focus, anomaly, and context-change handoff are unavailable because the bounded qualitative index is incomplete; no global driver was inferred from a selected subset."
|
|
110
|
+
])
|
|
86
111
|
];
|
|
87
112
|
return {
|
|
88
113
|
dataMode: "local_transcripts",
|
|
89
114
|
generatedAt: now.toISOString(),
|
|
90
115
|
coverage: {
|
|
91
116
|
filesParsed: options.filesParsed ?? 0,
|
|
117
|
+
qualitative: qualitativeCoverage,
|
|
92
118
|
supportedTranscriptAgents: supportedFormats.map((descriptor) => descriptor.id),
|
|
93
119
|
detectedAgents,
|
|
94
120
|
rateLimitMetadata: supportedFormats.map((descriptor) => ({
|
|
@@ -146,6 +172,14 @@ export function buildUsageGlance(calls, options = {}) {
|
|
|
146
172
|
execution: "copy_prompt",
|
|
147
173
|
automaticExecution: false
|
|
148
174
|
},
|
|
175
|
+
tokenExperiment: {
|
|
176
|
+
source: tokenExperiment
|
|
177
|
+
? "canonical_action_verification_projection"
|
|
178
|
+
: "not_available",
|
|
179
|
+
calculation: "core_experiment_evaluator",
|
|
180
|
+
cohort: "matched_local_sessions",
|
|
181
|
+
automaticExecution: false
|
|
182
|
+
},
|
|
149
183
|
network: {
|
|
150
184
|
uploaded: false
|
|
151
185
|
}
|
|
@@ -157,9 +191,114 @@ export function buildUsageGlance(calls, options = {}) {
|
|
|
157
191
|
anomaly,
|
|
158
192
|
sessionHealth,
|
|
159
193
|
primaryAction,
|
|
194
|
+
...(tokenExperiment ? { tokenExperiment } : {}),
|
|
160
195
|
caveats
|
|
161
196
|
};
|
|
162
197
|
}
|
|
198
|
+
function buildCoverageLimitedPrimaryAction(input) {
|
|
199
|
+
const project = safeActionMetadata(input.currentSession?.project, 80);
|
|
200
|
+
const status = input.coverage.status === "partial" ? "partial" : "not available";
|
|
201
|
+
return {
|
|
202
|
+
kind: "session_handoff",
|
|
203
|
+
intent: "inspect_current_work",
|
|
204
|
+
label: project ? `Refresh evidence · ${project}` : "Refresh evidence",
|
|
205
|
+
detail: `Main focus unavailable · qualitative index ${status}`,
|
|
206
|
+
...(project ? { project } : {}),
|
|
207
|
+
agentPrompt: [
|
|
208
|
+
"aibill's bounded qualitative evidence is incomplete.",
|
|
209
|
+
"Do not infer a global main focus, waste cause, or context change from the selected subset.",
|
|
210
|
+
`Run \`${aibillImproveCommandV0()}\` from the exact project root to refresh the private index, then review the new evidence before editing.`
|
|
211
|
+
].join("\n"),
|
|
212
|
+
source: "context_health_focus_and_reported_runway",
|
|
213
|
+
confidence: "low",
|
|
214
|
+
execution: "copy_prompt",
|
|
215
|
+
requiresUserConfirmation: true,
|
|
216
|
+
evidenceWindowDays: input.sessionHealth.deadContext.windowDays
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const actionVerificationStates = new Set([
|
|
220
|
+
"collect_baseline",
|
|
221
|
+
"approve_one_change",
|
|
222
|
+
"collect_post_change",
|
|
223
|
+
"review_measured_result",
|
|
224
|
+
"rollback",
|
|
225
|
+
"resolve_evidence",
|
|
226
|
+
"rolled_back",
|
|
227
|
+
"cancelled"
|
|
228
|
+
]);
|
|
229
|
+
const actionVerificationTones = new Set([
|
|
230
|
+
"neutral",
|
|
231
|
+
"attention",
|
|
232
|
+
"positive",
|
|
233
|
+
"negative"
|
|
234
|
+
]);
|
|
235
|
+
const actionVerificationEvidenceLabels = new Set([
|
|
236
|
+
"calculated",
|
|
237
|
+
"missing"
|
|
238
|
+
]);
|
|
239
|
+
const actionVerificationQualityLabels = new Set([
|
|
240
|
+
"held",
|
|
241
|
+
"regressed",
|
|
242
|
+
"insufficient"
|
|
243
|
+
]);
|
|
244
|
+
const actionVerificationQualityEvidence = new Set([
|
|
245
|
+
"verified",
|
|
246
|
+
"observed",
|
|
247
|
+
"user_declared",
|
|
248
|
+
"missing"
|
|
249
|
+
]);
|
|
250
|
+
const experimentIdPattern = /^tre_v0_[a-f0-9]{64}$/;
|
|
251
|
+
const findingIdPattern = /^wf_v0_[a-f0-9]{64}$/;
|
|
252
|
+
const candidateKeyPattern = /^wfc_v0_[a-f0-9]{64}$/;
|
|
253
|
+
/**
|
|
254
|
+
* Treat the optional adapter input as untrusted at runtime. A malformed or
|
|
255
|
+
* internally inconsistent projection is omitted instead of becoming a stale
|
|
256
|
+
* or invented Glance claim. This function deliberately does not derive any
|
|
257
|
+
* experiment result.
|
|
258
|
+
*/
|
|
259
|
+
function sanitizeActionVerificationProjection(input) {
|
|
260
|
+
if (!input || typeof input !== "object")
|
|
261
|
+
return undefined;
|
|
262
|
+
const safe = sanitizeStringMetadata(input);
|
|
263
|
+
if (safe.schemaVersion !== 0 ||
|
|
264
|
+
!experimentIdPattern.test(safe.experimentId) ||
|
|
265
|
+
!findingIdPattern.test(safe.findingId) ||
|
|
266
|
+
!candidateKeyPattern.test(safe.candidateKey) ||
|
|
267
|
+
!actionVerificationStates.has(safe.state) ||
|
|
268
|
+
!actionVerificationTones.has(safe.tone) ||
|
|
269
|
+
!actionVerificationEvidenceLabels.has(safe.evidenceLabel) ||
|
|
270
|
+
!actionVerificationQualityLabels.has(safe.qualityLabel) ||
|
|
271
|
+
!actionVerificationQualityEvidence.has(safe.qualityEvidence) ||
|
|
272
|
+
!isSafeExperimentCount(safe.baselineSessions) ||
|
|
273
|
+
!isSafeExperimentCount(safe.postChangeSessions) ||
|
|
274
|
+
!isSafeExperimentCount(safe.minimumSessions) ||
|
|
275
|
+
safe.minimumSessions < 1 ||
|
|
276
|
+
(safe.reductionPercent !== null && (!Number.isFinite(safe.reductionPercent) ||
|
|
277
|
+
safe.reductionPercent > 100 ||
|
|
278
|
+
safe.reductionPercent < -1_000_000))) {
|
|
279
|
+
return undefined;
|
|
280
|
+
}
|
|
281
|
+
const measured = safe.state === "review_measured_result";
|
|
282
|
+
const claimQualityEvidence = safe.qualityEvidence === "verified" ||
|
|
283
|
+
safe.qualityEvidence === "observed" ||
|
|
284
|
+
safe.qualityEvidence === "user_declared";
|
|
285
|
+
if (safe.reductionPercent !== null) {
|
|
286
|
+
const claimEvidenceIsComplete = safe.evidenceLabel === "calculated" &&
|
|
287
|
+
safe.qualityLabel === "held" &&
|
|
288
|
+
claimQualityEvidence;
|
|
289
|
+
const signMatchesState = (measured && safe.reductionPercent >= 0) ||
|
|
290
|
+
(safe.state === "rollback" && safe.reductionPercent < 0);
|
|
291
|
+
if (!claimEvidenceIsComplete || !signMatchesState)
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
else if (measured) {
|
|
295
|
+
return undefined;
|
|
296
|
+
}
|
|
297
|
+
return safe;
|
|
298
|
+
}
|
|
299
|
+
function isSafeExperimentCount(value) {
|
|
300
|
+
return Number.isSafeInteger(value) && value >= 0;
|
|
301
|
+
}
|
|
163
302
|
function toGlancePlan(agent, detectedPlans) {
|
|
164
303
|
const detected = detectedPlans.find((plan) => plan.agent === agent);
|
|
165
304
|
if (!detected)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared guided-answer classifier for the aibill improve/identify flows.
|
|
3
|
+
*
|
|
4
|
+
* One function, three consumers: the CLI terminal prompts (typed input and
|
|
5
|
+
* Enter-kept prefills), the CLI agent-draft screening lane, and the MCP
|
|
6
|
+
* `draft_improve_command` preview. Keeping a single pure module in core is
|
|
7
|
+
* what makes the "same classifier at composition and at Enter-accept"
|
|
8
|
+
* invariant true by construction rather than by parity discipline.
|
|
9
|
+
*
|
|
10
|
+
* Design: P0B_FLOW_DESIGN.md §2/§3b/§3c and AGENT_NATIVE_LOOP_DESIGN.md §2f
|
|
11
|
+
* (moved here from packages/cli/src/guidedPrompt.ts, verbatim; the path and
|
|
12
|
+
* credential predicates moved with it from projectAccountabilityState.ts so
|
|
13
|
+
* the floor relationship can never drift).
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Exported as the classification FLOOR for the guided-prompt engine: the
|
|
17
|
+
* per-prompt classifier must reject at least everything these predicates
|
|
18
|
+
* reject, so `parseDisplayLabel` can never abort on an answer the prompt
|
|
19
|
+
* already accepted (the Aug 17 incident class).
|
|
20
|
+
*/
|
|
21
|
+
export declare function isPathLike(value: string): boolean;
|
|
22
|
+
export declare function isCredentialLike(value: string): boolean;
|
|
23
|
+
export type GuidedFieldKind = "prose" | "name" | "team" | "role" | "optional" | "time" | "choice" | "approve";
|
|
24
|
+
export type ClassifyContext = {
|
|
25
|
+
example?: string;
|
|
26
|
+
/** Exact accepted tokens for choice fields, lowercase. */
|
|
27
|
+
choiceTokens?: readonly string[];
|
|
28
|
+
/** ISO instant the plan was approved (time fields). */
|
|
29
|
+
approvedAtIso?: string;
|
|
30
|
+
/** Clock override for tests. */
|
|
31
|
+
nowMs?: number;
|
|
32
|
+
/** Consecutive identical shell-rejections at this step (keep override). */
|
|
33
|
+
priorShellRejections?: number;
|
|
34
|
+
};
|
|
35
|
+
export type ClassifyResult = {
|
|
36
|
+
outcome: "accept";
|
|
37
|
+
value: string;
|
|
38
|
+
} | {
|
|
39
|
+
outcome: "navigate";
|
|
40
|
+
action: "back" | "cancel";
|
|
41
|
+
} | {
|
|
42
|
+
outcome: "skip";
|
|
43
|
+
} | {
|
|
44
|
+
outcome: "reject";
|
|
45
|
+
code: RejectCode;
|
|
46
|
+
message: string;
|
|
47
|
+
};
|
|
48
|
+
export type RejectCode = "control" | "empty" | "shell" | "path" | "credential" | "reserved" | "timestamp_shaped" | "length" | "substance" | "time_invalid" | "time_before_approval" | "time_future" | "choice" | "approve_case";
|
|
49
|
+
export declare function looksLikeCredential(answer: string): boolean;
|
|
50
|
+
export declare function classifyGuidedAnswer(kind: GuidedFieldKind, rawInput: string, context?: ClassifyContext): ClassifyResult;
|
|
51
|
+
//# sourceMappingURL=guidedAnswer.d.ts.map
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared guided-answer classifier for the aibill improve/identify flows.
|
|
3
|
+
*
|
|
4
|
+
* One function, three consumers: the CLI terminal prompts (typed input and
|
|
5
|
+
* Enter-kept prefills), the CLI agent-draft screening lane, and the MCP
|
|
6
|
+
* `draft_improve_command` preview. Keeping a single pure module in core is
|
|
7
|
+
* what makes the "same classifier at composition and at Enter-accept"
|
|
8
|
+
* invariant true by construction rather than by parity discipline.
|
|
9
|
+
*
|
|
10
|
+
* Design: P0B_FLOW_DESIGN.md §2/§3b/§3c and AGENT_NATIVE_LOOP_DESIGN.md §2f
|
|
11
|
+
* (moved here from packages/cli/src/guidedPrompt.ts, verbatim; the path and
|
|
12
|
+
* credential predicates moved with it from projectAccountabilityState.ts so
|
|
13
|
+
* the floor relationship can never drift).
|
|
14
|
+
*/
|
|
15
|
+
/* ------------------------------------------------------------------ */
|
|
16
|
+
/* Floor predicates (accountability backstop) */
|
|
17
|
+
/* ------------------------------------------------------------------ */
|
|
18
|
+
/**
|
|
19
|
+
* Exported as the classification FLOOR for the guided-prompt engine: the
|
|
20
|
+
* per-prompt classifier must reject at least everything these predicates
|
|
21
|
+
* reject, so `parseDisplayLabel` can never abort on an answer the prompt
|
|
22
|
+
* already accepted (the Aug 17 incident class).
|
|
23
|
+
*/
|
|
24
|
+
export function isPathLike(value) {
|
|
25
|
+
return /^(?:~?[\\/]|\.{1,2}(?:[\\/]|$)|[A-Za-z]:[\\/]|\\\\)/u.test(value) ||
|
|
26
|
+
/(?:^|[\\/])\.\.(?:[\\/]|$)/u.test(value) || value.includes("\\");
|
|
27
|
+
}
|
|
28
|
+
export function isCredentialLike(value) {
|
|
29
|
+
return /(?:sk-(?:ant-|proj-)?|sk_|gh[pousr]_|github_pat_|npm_|AIza|xox[baprs]-|glpat-|AKIA)[A-Za-z0-9_-]{8,}/i
|
|
30
|
+
.test(value) ||
|
|
31
|
+
/(?:api[_ -]?key|access[_ -]?token|auth[_ -]?token|secret|password)\s*[:=]\s*\S+/i
|
|
32
|
+
.test(value);
|
|
33
|
+
}
|
|
34
|
+
const unambiguousBinaries = new Set([
|
|
35
|
+
"node", "npm", "npx", "pnpm", "yarn", "bun", "bunx", "deno", "tsx", "ts-node",
|
|
36
|
+
"git", "gh", "curl", "wget", "bash", "sh", "zsh", "fish", "pwsh", "powershell",
|
|
37
|
+
"python", "python3", "pip", "pip3", "pipx", "uv", "uvx", "poetry", "pytest",
|
|
38
|
+
"vitest", "jest", "brew", "apt", "docker", "kubectl", "cargo", "rustc",
|
|
39
|
+
"dotnet", "mvn", "gradle", "terraform", "ssh", "scp", "rsync", "sudo",
|
|
40
|
+
"chmod", "chown", "xargs", "grep", "rg", "sed", "awk", "tar", "zip", "unzip",
|
|
41
|
+
"aibill", "ai-spend-agent", "vim", "nano", "code", "dir", "del", "robocopy"
|
|
42
|
+
]);
|
|
43
|
+
/** Common English verbs that are also binaries: reject only with corroboration. */
|
|
44
|
+
const ambiguousVerbs = new Set([
|
|
45
|
+
"make", "open", "find", "go", "date", "kill", "top", "head", "tail", "touch",
|
|
46
|
+
"export", "source", "alias", "echo", "type", "cd", "ls", "cat", "cp", "mv",
|
|
47
|
+
"rm", "printf", "less", "ps", "copy", "move"
|
|
48
|
+
]);
|
|
49
|
+
const reservedVocabulary = new Set([
|
|
50
|
+
"held", "passed", "failed", "missing", "regressed", "approve", "approved",
|
|
51
|
+
"yes", "no", "y", "n", "p", "f", "h", "r", "m", "now", "skip", "keep"
|
|
52
|
+
]);
|
|
53
|
+
/**
|
|
54
|
+
* The accountability backstop predicate (`isCredentialLike`) is the FLOOR:
|
|
55
|
+
* this classifier must reject at least everything `parseDisplayLabel` would
|
|
56
|
+
* reject, or a validated answer could still abort later (B2 QA blocker B1).
|
|
57
|
+
* These extra patterns sit on top of the floor.
|
|
58
|
+
*/
|
|
59
|
+
const extraCredentialPattern = /(authorization\s*:\s*(?:bearer|basic)\s+\S+|-----BEGIN [A-Z ]*PRIVATE KEY-----|(?:api[ _-]?key|token|password|secret)\s*[:=]\s*\S+)/i;
|
|
60
|
+
export function looksLikeCredential(answer) {
|
|
61
|
+
return isCredentialLike(answer) || extraCredentialPattern.test(answer);
|
|
62
|
+
}
|
|
63
|
+
const pathExtensionPattern = /\S+\.(?:js|ts|mjs|cjs|jsx|tsx|json|sh|bash|py|rb|go|rs|md|yml|yaml|toml|lock|xml|ps1|bat|cmd|gradle)(?:$|\s)/i;
|
|
64
|
+
/** Strict full date-time with zone, mirroring the CLI's validIsoString. */
|
|
65
|
+
const strictIsoPattern = /^\d{4}-\d{2}-\d{2}[Tt ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:[Zz]|[+-]\d{2}:?\d{2})$/;
|
|
66
|
+
const futureToleranceMs = 2 * 60 * 1000;
|
|
67
|
+
/**
|
|
68
|
+
* Exact non-answer phrases (normalized: lowercase, punctuation stripped).
|
|
69
|
+
* A plan sentence must be actionable later; "i am not sure" passes the
|
|
70
|
+
* two-word substance bar but records a safety net that cannot be executed.
|
|
71
|
+
* Exact match only — a real sentence that merely CONTAINS one still passes.
|
|
72
|
+
*/
|
|
73
|
+
const nonAnswerPhrases = new Set([
|
|
74
|
+
"i am not sure", "im not sure", "not sure", "unsure", "i am unsure",
|
|
75
|
+
"idk", "i dont know", "dont know", "no idea", "dunno",
|
|
76
|
+
"no se", "no sé", "ni idea",
|
|
77
|
+
"whatever", "anything", "nothing", "none", "na", "tbd",
|
|
78
|
+
"help", "test", "testing", "asdf"
|
|
79
|
+
]);
|
|
80
|
+
function isNonAnswer(lowered) {
|
|
81
|
+
const normalized = lowered.replace(/[.,!?'’]/g, "").replace(/\s+/g, " ").trim();
|
|
82
|
+
return nonAnswerPhrases.has(normalized);
|
|
83
|
+
}
|
|
84
|
+
const choiceWordAliases = {
|
|
85
|
+
yes: "y",
|
|
86
|
+
no: "n",
|
|
87
|
+
passed: "p",
|
|
88
|
+
failed: "f",
|
|
89
|
+
held: "h",
|
|
90
|
+
regressed: "r",
|
|
91
|
+
missing: "m"
|
|
92
|
+
};
|
|
93
|
+
function stripQuotes(token) {
|
|
94
|
+
return token.replace(/^["'`]+/, "").replace(/["'`]+$/, "");
|
|
95
|
+
}
|
|
96
|
+
function stripEnvPrefixes(tokens) {
|
|
97
|
+
let index = 0;
|
|
98
|
+
while (index < tokens.length &&
|
|
99
|
+
(/^[A-Za-z_][A-Za-z0-9_]*=\S*$/.test(tokens[index]) ||
|
|
100
|
+
tokens[index] === "sudo" || tokens[index] === "env")) {
|
|
101
|
+
index += 1;
|
|
102
|
+
}
|
|
103
|
+
return tokens.slice(index);
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* §2f rule 2 (AGENT_NATIVE_LOOP_DESIGN.md): a pipe straight into an
|
|
107
|
+
* interpreter, spaced or not — `|sh`, `curl …|bash`, `|python3 -c …`.
|
|
108
|
+
*/
|
|
109
|
+
const pipeToInterpreterPattern = /\|\s*(?:sh|bash|zsh|fish|dash|node|python3?|perl|ruby|pwsh)\b/i;
|
|
110
|
+
/** §2f rule 4: free-standing multi-letter short flag (` -rf`), corroboration only. */
|
|
111
|
+
const multiLetterShortFlagPattern = /(?:^|\s)-[A-Za-z]{2,}(?=\s|$)/;
|
|
112
|
+
function looksLikeShellCommand(answer) {
|
|
113
|
+
if (/^[$%#>] ?/.test(answer))
|
|
114
|
+
return true;
|
|
115
|
+
if (/&&|\|\||`|\$\(|>>|<<|2>&1| \| | > | < /.test(answer))
|
|
116
|
+
return true;
|
|
117
|
+
if (pipeToInterpreterPattern.test(answer))
|
|
118
|
+
return true;
|
|
119
|
+
if (/(?:^|\s)--[A-Za-z][\w-]*/.test(answer))
|
|
120
|
+
return true;
|
|
121
|
+
if (/(?:^|\s)-[A-Za-z](?=\s|$)/.test(answer))
|
|
122
|
+
return true;
|
|
123
|
+
// PowerShell Verb-Noun cmdlet shape (Get-ChildItem, Remove-Item …).
|
|
124
|
+
if (/^[A-Z][a-z]+-[A-Z][A-Za-z]+(?:\s|$)/.test(answer))
|
|
125
|
+
return true;
|
|
126
|
+
// §2f rule 1: strip any leading run of whitespace/quote/chain sigils
|
|
127
|
+
// before first-token analysis (covers `"; rm …`, `'; …`, `| sh`, `& …`
|
|
128
|
+
// fragments). If the strip removed a `;`, `|`, or `&`, the text STARTS
|
|
129
|
+
// like a command chain — that is a reject on its own. A plain leading
|
|
130
|
+
// quotation mark followed by words strips harmlessly and never rejects.
|
|
131
|
+
const leadingSigils = /^[\s"'`;|&]+/.exec(answer)?.[0] ?? "";
|
|
132
|
+
if (/[;|&]/.test(leadingSigils))
|
|
133
|
+
return true;
|
|
134
|
+
const body = answer.slice(leadingSigils.length);
|
|
135
|
+
const tokens = stripEnvPrefixes(body.split(/\s+/).filter(Boolean));
|
|
136
|
+
// Second signals, shared by first-token ambiguity and §2f rule 3: a
|
|
137
|
+
// path-like token, a file extension, or a multi-letter short flag
|
|
138
|
+
// (`rm -rf …` — rule 4; needs whitespace before the `-`, so hyphenated
|
|
139
|
+
// words like `well-known` or `re-use` can never match).
|
|
140
|
+
const hasPathToken = tokens.some((token) => /^(?:\/|\.\/|\.\.\/|~\/)/.test(token) || token.includes("/"));
|
|
141
|
+
const hasExtension = pathExtensionPattern.test(body);
|
|
142
|
+
const hasShortFlag = multiLetterShortFlagPattern.test(body);
|
|
143
|
+
const first = stripQuotes(tokens[0] ?? "").toLowerCase();
|
|
144
|
+
if (unambiguousBinaries.has(first))
|
|
145
|
+
return true;
|
|
146
|
+
if (ambiguousVerbs.has(first)) {
|
|
147
|
+
// Ambiguous English verbs reject only with a second signal: a path-like
|
|
148
|
+
// token, a file extension, a corroborating flag, or a terse
|
|
149
|
+
// all-lowercase fragment that reads like a command line, not a sentence.
|
|
150
|
+
const terseLowercase = tokens.length <= 2 && body === body.toLowerCase();
|
|
151
|
+
if (hasPathToken || hasExtension || hasShortFlag || terseLowercase)
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
// §2f rule 3: `;` chained command starts. A `;` followed by an unambiguous
|
|
155
|
+
// binary rejects outright; followed by an ambiguous verb it rejects only
|
|
156
|
+
// with a second signal — English semicolons ("…workflow; keep the earlier
|
|
157
|
+
// settings", "…change; go back to the prior flow") survive.
|
|
158
|
+
for (const chained of body.matchAll(/;\s*([^\s;|&]+)/g)) {
|
|
159
|
+
const chainedFirst = stripQuotes(chained[1]).toLowerCase();
|
|
160
|
+
if (unambiguousBinaries.has(chainedFirst))
|
|
161
|
+
return true;
|
|
162
|
+
if (ambiguousVerbs.has(chainedFirst) &&
|
|
163
|
+
(hasPathToken || hasExtension || hasShortFlag)) {
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
function looksLikePath(answer, kind) {
|
|
170
|
+
// The accountability backstop predicate is the floor: it covers leading
|
|
171
|
+
// slashes and dot-segments, drive letters, backslashes, and bare ".".
|
|
172
|
+
if (isPathLike(answer))
|
|
173
|
+
return true;
|
|
174
|
+
if (kind === "prose") {
|
|
175
|
+
if (pathExtensionPattern.test(answer))
|
|
176
|
+
return true;
|
|
177
|
+
const slashTokens = answer.split(/\s+/).filter((token) => token.includes("/"));
|
|
178
|
+
return slashTokens.length >= 2;
|
|
179
|
+
}
|
|
180
|
+
// Name-like fields: a slashed token is a path; a bare file-extension token
|
|
181
|
+
// counts only when it IS the whole answer — "Node.js Guild" is a team.
|
|
182
|
+
if (/\S+\/\S+/.test(answer))
|
|
183
|
+
return true;
|
|
184
|
+
return pathExtensionPattern.test(answer) && !/\s/.test(answer.trim());
|
|
185
|
+
}
|
|
186
|
+
function byteLength(value) {
|
|
187
|
+
return Buffer.byteLength(value.normalize("NFC"), "utf8");
|
|
188
|
+
}
|
|
189
|
+
function hasControlOrFormatCharacters(value) {
|
|
190
|
+
// C0/C1 controls including embedded newlines and DEL (tab and trailing
|
|
191
|
+
// CR are normalized earlier), plus the invisible/directional format
|
|
192
|
+
// characters that can spoof what the review screen appears to say.
|
|
193
|
+
// ZWNJ/ZWJ (U+200C/U+200D) are deliberately ALLOWED: they are standard
|
|
194
|
+
// orthography in Persian and other scripts and in emoji families.
|
|
195
|
+
if (/[\u0000-\u0008\u000A-\u001F\u007F-\u009F\u2028\u2029]/.test(value))
|
|
196
|
+
return true;
|
|
197
|
+
return /[\u00AD\u061C\u200B\u200E\u200F\u202A-\u202E\u2060-\u2064\u2066-\u2069\uFEFF]/.test(value);
|
|
198
|
+
}
|
|
199
|
+
function hasUnpairedSurrogate(value) {
|
|
200
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
201
|
+
const code = value.charCodeAt(index);
|
|
202
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
203
|
+
const next = value.charCodeAt(index + 1);
|
|
204
|
+
if (!(next >= 0xdc00 && next <= 0xdfff))
|
|
205
|
+
return true;
|
|
206
|
+
index += 1;
|
|
207
|
+
}
|
|
208
|
+
else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return false;
|
|
213
|
+
}
|
|
214
|
+
export function classifyGuidedAnswer(kind, rawInput, context = {}) {
|
|
215
|
+
const normalizedTabs = rawInput.replace(/\t/g, " ").replace(/\r$/, "");
|
|
216
|
+
const answer = normalizedTabs.trim();
|
|
217
|
+
const lowered = answer.toLowerCase();
|
|
218
|
+
const reject = (code, message) => ({
|
|
219
|
+
outcome: "reject", code, message
|
|
220
|
+
});
|
|
221
|
+
const exampleSuffix = context.example ? ` e.g. ${context.example}` : "";
|
|
222
|
+
// Navigation pre-pass (every field).
|
|
223
|
+
if (lowered === "back" || lowered === "b")
|
|
224
|
+
return { outcome: "navigate", action: "back" };
|
|
225
|
+
if (lowered === "cancel" || lowered === "q" || lowered === "quit" || lowered === "exit") {
|
|
226
|
+
return { outcome: "navigate", action: "cancel" };
|
|
227
|
+
}
|
|
228
|
+
if (kind === "optional" && (answer === "" || lowered === "skip")) {
|
|
229
|
+
return { outcome: "skip" };
|
|
230
|
+
}
|
|
231
|
+
if (hasControlOrFormatCharacters(answer) || hasUnpairedSurrogate(answer)) {
|
|
232
|
+
return reject("control", "That answer carried hidden control characters (usually a stray paste). Type it as plain text.");
|
|
233
|
+
}
|
|
234
|
+
if (answer === "") {
|
|
235
|
+
if (kind === "approve") {
|
|
236
|
+
// An empty answer at the approval screen is a decline, not an error.
|
|
237
|
+
return { outcome: "navigate", action: "cancel" };
|
|
238
|
+
}
|
|
239
|
+
return reject("empty", "This step needs an answer in words. Type it, or type back or cancel.");
|
|
240
|
+
}
|
|
241
|
+
if (looksLikeCredential(answer)) {
|
|
242
|
+
// Never echo, never store: callers must discard this input entirely.
|
|
243
|
+
return reject("credential", "That looks like it contains a credential. aibill never stores credentials — that answer was discarded. Type it again without the secret.");
|
|
244
|
+
}
|
|
245
|
+
switch (kind) {
|
|
246
|
+
case "approve": {
|
|
247
|
+
if (answer === "APPROVE")
|
|
248
|
+
return { outcome: "accept", value: answer };
|
|
249
|
+
// Clear approval intent gets the nudge, never a silent decline:
|
|
250
|
+
// APPROVED, aprove, i approve, full-width IME APPROVE, yes/y.
|
|
251
|
+
const folded = answer.normalize("NFKC").toUpperCase();
|
|
252
|
+
if (folded.includes("APPROV") || folded.includes("APROVE") ||
|
|
253
|
+
lowered === "yes" || lowered === "y") {
|
|
254
|
+
return reject("approve_case", "Approval must be typed APPROVE, in capitals, so it cannot happen by accident.");
|
|
255
|
+
}
|
|
256
|
+
// Any other answer is a decline — an answer, not an error.
|
|
257
|
+
return { outcome: "navigate", action: "cancel" };
|
|
258
|
+
}
|
|
259
|
+
case "choice": {
|
|
260
|
+
const tokens = context.choiceTokens ?? [];
|
|
261
|
+
if (tokens.includes(lowered))
|
|
262
|
+
return { outcome: "accept", value: lowered };
|
|
263
|
+
// Exact full words map to their canonical letter, but only within
|
|
264
|
+
// their own question family: "no" at a p/f/n question must reprompt,
|
|
265
|
+
// never silently become "n". Never prefix-match either — "probably
|
|
266
|
+
// failed" or "not sure" reprompts, not guesses.
|
|
267
|
+
const alias = choiceWordAliases[lowered];
|
|
268
|
+
if (alias !== undefined && tokens.includes(alias)) {
|
|
269
|
+
const applies = lowered === "yes" || lowered === "no" ? tokens.includes("y") :
|
|
270
|
+
lowered === "passed" || lowered === "failed" ? tokens.includes("p") :
|
|
271
|
+
tokens.includes("h");
|
|
272
|
+
if (applies)
|
|
273
|
+
return { outcome: "accept", value: alias };
|
|
274
|
+
}
|
|
275
|
+
if (tokens.includes("p")) {
|
|
276
|
+
return reject("choice", "Answer p (passed), f (failed), or n (not run yet).");
|
|
277
|
+
}
|
|
278
|
+
if (tokens.includes("h")) {
|
|
279
|
+
return reject("choice", "Answer h (held), r (regressed), or m (cannot say).");
|
|
280
|
+
}
|
|
281
|
+
return reject("choice", `Answer one of: ${tokens.join(", ")}.`);
|
|
282
|
+
}
|
|
283
|
+
case "time": {
|
|
284
|
+
if (lowered === "now") {
|
|
285
|
+
return { outcome: "accept", value: new Date(context.nowMs ?? Date.now()).toISOString() };
|
|
286
|
+
}
|
|
287
|
+
if (!strictIsoPattern.test(answer)) {
|
|
288
|
+
return reject("time_invalid", "That is not a UTC ISO-8601 time. e.g. 2026-08-17T14:03:00Z — or type now if it just finished.");
|
|
289
|
+
}
|
|
290
|
+
const parsed = Date.parse(answer);
|
|
291
|
+
if (!Number.isFinite(parsed)) {
|
|
292
|
+
return reject("time_invalid", "That is not a UTC ISO-8601 time. e.g. 2026-08-17T14:03:00Z — or type now if it just finished.");
|
|
293
|
+
}
|
|
294
|
+
const approvedAt = context.approvedAtIso ? Date.parse(context.approvedAtIso) : undefined;
|
|
295
|
+
if (context.approvedAtIso !== undefined && !Number.isFinite(approvedAt ?? Number.NaN)) {
|
|
296
|
+
// Fail closed: an unreadable approval time must never silently
|
|
297
|
+
// disable the after-approval check.
|
|
298
|
+
return reject("time_invalid", "The approval record's own time is unreadable, so this time cannot be checked. Type cancel and rerun this command.");
|
|
299
|
+
}
|
|
300
|
+
if (approvedAt !== undefined && parsed <= approvedAt) {
|
|
301
|
+
return reject("time_before_approval", `That time is not after the approval at ${context.approvedAtIso}. A change cannot be applied before it was approved. Paste the time the agent reported.`);
|
|
302
|
+
}
|
|
303
|
+
if (parsed > (context.nowMs ?? Date.now()) + futureToleranceMs) {
|
|
304
|
+
return reject("time_future", "That time is in the future. Paste the actual reported time.");
|
|
305
|
+
}
|
|
306
|
+
return { outcome: "accept", value: new Date(parsed).toISOString() };
|
|
307
|
+
}
|
|
308
|
+
default:
|
|
309
|
+
break;
|
|
310
|
+
}
|
|
311
|
+
if (looksLikeShellCommand(answer)) {
|
|
312
|
+
const message = kind === "prose"
|
|
313
|
+
? "That looks like a shell command, not an answer. Nothing runs here — describe it in words."
|
|
314
|
+
: "That looks like a shell command, not a name. Answer with the name in words.";
|
|
315
|
+
return reject("shell", message);
|
|
316
|
+
}
|
|
317
|
+
if (kind === "prose" && lowered === "keep" && (context.priorShellRejections ?? 0) >= 2) {
|
|
318
|
+
// After repeated identical shell rejections the user may type `keep` to
|
|
319
|
+
// record their exact text as words. The caller substitutes the last
|
|
320
|
+
// rejected line; this sentinel value never reaches storage.
|
|
321
|
+
return { outcome: "accept", value: "keep" };
|
|
322
|
+
}
|
|
323
|
+
if (looksLikePath(answer, kind)) {
|
|
324
|
+
return reject("path", "That looks like a file path. Describe it in words instead — the answer must read as a sentence, not a location.");
|
|
325
|
+
}
|
|
326
|
+
if (kind === "name" || kind === "team" || kind === "role" || kind === "optional") {
|
|
327
|
+
if (reservedVocabulary.has(lowered)) {
|
|
328
|
+
const message = kind === "role"
|
|
329
|
+
? `"${answer}" is aibill's own vocabulary, not a role. Answer with your real job role, in words.${exampleSuffix}`
|
|
330
|
+
: `That is aibill vocabulary, not a name. Answer in your own words.${exampleSuffix}`;
|
|
331
|
+
return reject("reserved", message);
|
|
332
|
+
}
|
|
333
|
+
if (strictIsoPattern.test(answer)) {
|
|
334
|
+
return reject("timestamp_shaped", `That is a time, not a name.${exampleSuffix}`);
|
|
335
|
+
}
|
|
336
|
+
if (byteLength(answer) > 192) {
|
|
337
|
+
return reject("length", "That name is longer than aibill can store (192 bytes). Use a shorter form.");
|
|
338
|
+
}
|
|
339
|
+
return { outcome: "accept", value: answer.normalize("NFC") };
|
|
340
|
+
}
|
|
341
|
+
// prose
|
|
342
|
+
if (answer.length > 1000) {
|
|
343
|
+
return reject("length", "Keep it to one or two short sentences (under 1,000 characters).");
|
|
344
|
+
}
|
|
345
|
+
if (answer.split(/\s+/).filter(Boolean).length < 2 || isNonAnswer(lowered)) {
|
|
346
|
+
return reject("substance", `That is not something aibill can hold you to later. Write what should actually happen — or type back or cancel if you are not ready.${exampleSuffix}`);
|
|
347
|
+
}
|
|
348
|
+
// NFC like the name fields: the rollback sentence is later re-typed and
|
|
349
|
+
// compared by hash, so composition differences must not fail the match.
|
|
350
|
+
return { outcome: "accept", value: answer.normalize("NFC") };
|
|
351
|
+
}
|
|
352
|
+
//# sourceMappingURL=guidedAnswer.js.map
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export * from "./analyze.js";
|
|
2
|
+
export * from "./actionPlanner.js";
|
|
3
|
+
export * from "./actionVerification.js";
|
|
2
4
|
export * from "./agentInventory.js";
|
|
3
5
|
export * from "./agentEconomicsReceipt.js";
|
|
4
6
|
export * from "./activitySnapshot.js";
|
|
@@ -12,16 +14,26 @@ export * from "./discovery.js";
|
|
|
12
14
|
export * from "./glance.js";
|
|
13
15
|
export * from "./toolInvocations.js";
|
|
14
16
|
export * from "./insights.js";
|
|
15
|
-
export { aggregateCalls, dedupeCumulativeSessionCalls, latestObservedWorkingDirectory, loadLocalAgentFinancialUsage, loadLocalAgentUsage, parseClaudeCodeTranscript, parseCodexRollout, sanitizeLocalActivityText } from "./localAgentLogs.js";
|
|
16
|
-
export type { LocalAgentActivity, LocalAgentCall, LocalAgentFinancialLogOptions, LocalAgentLogDiagnostic, LocalAgentLogDiagnosticCode, LocalAgentLogOptions, LocalAgentLogResult, LocalAgentRateLimitSnapshot, LocalAgentRateLimitWindow, LocalAgentSourceScan, LocalAgentTurnUsage } from "./localAgentLogs.js";
|
|
17
|
+
export { aggregateCalls, codexHeaderProbesPerScan, dedupeCumulativeSessionCalls, defaultStreamedBytesPerRun, hasCompleteQualitativeCoverage, hasExactSelectedQualitativeEvidence, latestObservedWorkingDirectory, loadLocalAgentActionEvidence, loadLocalAgentFinancialUsage, loadLocalAgentUsage, localAgentQualitativeParserVersion, parseClaudeCodeTranscript, parseCodexRollout, SAFE_QUALITATIVE_SCAN_POLICY, sanitizeLocalActivityText } from "./localAgentLogs.js";
|
|
18
|
+
export type { LocalAgentActivity, LocalAgentCall, LocalAgentCompletionEvidence, LocalAgentFinancialLogOptions, LocalAgentOwnershipIndexAdapter, LocalAgentOwnershipRecord, LocalAgentQualitativeIndexAdapter, LocalAgentQualitativeIndexKey, LocalAgentQualitativeIndexValue, LocalAgentQualitativeScanPolicy, LocalAgentStreamCheckpointAdapter, LocalAgentStreamCheckpointRecord, LocalAgentLogDiagnostic, LocalAgentLogDiagnosticCode, LocalAgentLogOptions, LocalAgentLogResult, LocalAgentRateLimitSnapshot, LocalAgentRateLimitWindow, LocalAgentSourceScan, LocalAgentTokenComponentEvidence, LocalAgentTurnUsage } from "./localAgentLogs.js";
|
|
17
19
|
export * from "./localAgentFormats/registry.js";
|
|
18
20
|
export type * from "./localAgentFormats/types.js";
|
|
19
21
|
export * from "./modelPricing.js";
|
|
20
22
|
export * from "./planDetection.js";
|
|
21
23
|
export * from "./planMath.js";
|
|
24
|
+
export * from "./projectEconomics.js";
|
|
25
|
+
export * from "./resultCard.js";
|
|
26
|
+
export * from "./projectEconomicsBuilder.js";
|
|
27
|
+
export * from "./qualitativeIndexCache.js";
|
|
28
|
+
export * from "./projectIndexStore.js";
|
|
29
|
+
export * from "./runtimeCommands.js";
|
|
30
|
+
export * from "./guidedAnswer.js";
|
|
31
|
+
export * from "./agentDraftToken.js";
|
|
32
|
+
export * from "./agentLoopContract.js";
|
|
22
33
|
export * from "./sampleData.js";
|
|
23
34
|
export * from "./scanGuard.js";
|
|
24
35
|
export * from "./schema.js";
|
|
36
|
+
export * from "./sessionVitals.js";
|
|
25
37
|
export * from "./sourceRegistry.js";
|
|
26
38
|
export * from "./sourceStatus.js";
|
|
27
39
|
export * from "./stateTrust.js";
|