@frontera-sdk/cli 1.52.38 → 1.52.40
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 +9 -0
- package/package.json +6 -4
- package/src/adopt.ts +6 -2
- package/src/api/apps-api.ts +86 -4
- package/src/api/blueprint-authoring-api.ts +35 -11
- package/src/api/dataset-api.ts +29 -0
- package/src/api/governed-action-api.ts +90 -2
- package/src/api/platform-api.ts +87 -2
- package/src/api/workflow-api.ts +33 -2
- package/src/app-contract.ts +156 -0
- package/src/app-lock.ts +125 -0
- package/src/args.ts +14 -3
- package/src/auth-verify.ts +10 -0
- package/src/automation-template.ts +12 -1
- package/src/blueprint/action-rules.ts +516 -0
- package/src/blueprint/action-semantic-fit.ts +205 -0
- package/src/blueprint/compile.ts +51 -2
- package/src/blueprint/diff.ts +34 -2
- package/src/blueprint/metric-query.ts +215 -0
- package/src/blueprint/metric-rules.ts +122 -0
- package/src/blueprint/object-sets.ts +116 -0
- package/src/blueprint/ontology-edit-plan.ts +48 -4
- package/src/blueprint/projection.ts +24 -1
- package/src/blueprint/scaffold.ts +84 -42
- package/src/commands/action/available.ts +1 -1
- package/src/commands/action/criteria-refusal.ts +84 -0
- package/src/commands/action/decide.ts +5 -2
- package/src/commands/action/deploy.ts +13 -4
- package/src/commands/action/list.ts +134 -19
- package/src/commands/action/requests.ts +34 -6
- package/src/commands/action/submit.ts +28 -13
- package/src/commands/agent/index-commands.ts +69 -7
- package/src/commands/agent/resolve.ts +2 -0
- package/src/commands/app/checks.ts +44 -0
- package/src/commands/app/deploy.ts +243 -60
- package/src/commands/app/deployments.ts +97 -0
- package/src/commands/app/dev.ts +103 -11
- package/src/commands/app/init.ts +36 -3
- package/src/commands/app/promote.ts +45 -19
- package/src/commands/app/pull.ts +3 -1
- package/src/commands/app/rollback.ts +46 -0
- package/src/commands/app/sdk.ts +54 -8
- package/src/commands/app/unpin.ts +27 -0
- package/src/commands/app/versions.ts +1 -1
- package/src/commands/automation/bindings.ts +4 -2
- package/src/commands/automation/build-entry.ts +4 -1
- package/src/commands/automation/index-commands.ts +3 -1
- package/src/commands/automation/pull.ts +6 -1
- package/src/commands/automation/run.ts +34 -9
- package/src/commands/blueprint/bind.ts +2 -1
- package/src/commands/blueprint/declarative.ts +331 -31
- package/src/commands/blueprint/get.ts +6 -2
- package/src/commands/blueprint/grants.ts +64 -10
- package/src/commands/blueprint/metric.ts +108 -0
- package/src/commands/blueprint/object-sets.ts +71 -0
- package/src/commands/blueprint/query.ts +155 -31
- package/src/commands/dataset/index-commands.ts +156 -7
- package/src/commands/knowledge/index-commands.ts +122 -20
- package/src/commands/local/check.ts +657 -44
- package/src/commands/local/data.ts +33 -12
- package/src/commands/local/dev.ts +875 -88
- package/src/commands/local/docs-mcp.ts +10 -18
- package/src/commands/local/index-commands.ts +300 -63
- package/src/commands/local/init.ts +18 -5
- package/src/commands/local/key.ts +0 -3
- package/src/commands/local/providers.ts +0 -7
- package/src/commands/local/remove.ts +116 -0
- package/src/commands/local/schema.ts +3 -4
- package/src/commands/local/sdk-refresh.ts +59 -0
- package/src/commands/local/snapshot.ts +100 -0
- package/src/commands/local/sync-request.ts +320 -13
- package/src/commands/local/sync.ts +102 -5
- package/src/commands/mcp/serve.ts +34 -0
- package/src/commands/registry.ts +52 -8
- package/src/commands/types.ts +29 -0
- package/src/commands/workflow/archive.ts +46 -0
- package/src/commands/workflow/get.ts +2 -1
- package/src/commands/workflow/index-commands.ts +5 -0
- package/src/commands/workflow/push.ts +49 -2
- package/src/commands/workflow/run.ts +65 -7
- package/src/commands/workflow/runs.ts +9 -6
- package/src/commands/workflow/webhook-test.ts +166 -0
- package/src/config.ts +43 -6
- package/src/dev-broker.ts +52 -0
- package/src/exit.ts +103 -4
- package/src/flag-help.ts +77 -17
- package/src/forge/touches.ts +15 -12
- package/src/function-outside-imports.ts +89 -0
- package/src/harness.ts +42 -24
- package/src/heal.ts +112 -1
- package/src/local/fix-words.ts +12 -0
- package/src/local/function-bundle-recovery.ts +775 -0
- package/src/local/local-actions.ts +103 -10
- package/src/local/local-agent-action-grants.ts +210 -0
- package/src/local/local-agent-checks.ts +232 -0
- package/src/local/local-agent-grants.ts +29 -7
- package/src/local/local-agent-lean-plugins.ts +68 -0
- package/src/local/local-agent-missing-tools.ts +68 -0
- package/src/local/local-agent-name.ts +27 -0
- package/src/local/local-app-code.ts +191 -0
- package/src/local/local-app-dev-files.ts +186 -0
- package/src/local/local-app-dev-session.ts +88 -0
- package/src/local/local-app-lock-install.ts +114 -0
- package/src/local/local-app-pulled.ts +167 -0
- package/src/local/local-app-restore.ts +111 -0
- package/src/local/local-app-runtime.ts +68 -0
- package/src/local/local-app-sdk-paths.ts +149 -0
- package/src/local/local-app-sdk.ts +509 -0
- package/src/local/local-applied.ts +211 -21
- package/src/local/local-apply-history.ts +84 -0
- package/src/local/local-apply-summary.ts +86 -0
- package/src/local/local-apply-watchdog.ts +95 -0
- package/src/local/local-apps.ts +180 -7
- package/src/local/local-blueprint-errors.ts +198 -0
- package/src/local/local-blueprint-outline.ts +31 -12
- package/src/local/local-blueprint-schemas.ts +582 -0
- package/src/local/local-build-inputs.ts +48 -0
- package/src/local/local-build-summary.ts +495 -28
- package/src/local/local-case-evidence.ts +120 -0
- package/src/local/local-case-not-run.ts +153 -0
- package/src/local/local-chat-apps.ts +93 -16
- package/src/local/local-check-inherited.ts +83 -0
- package/src/local/local-cities.ts +89 -0
- package/src/local/local-component-health.ts +119 -0
- package/src/local/local-control.ts +34 -7
- package/src/local/local-db-snapshot.ts +348 -0
- package/src/local/local-dev-references.ts +346 -35
- package/src/local/local-dev-sync.ts +136 -7
- package/src/local/local-docker.ts +35 -1
- package/src/local/local-docs-search.ts +1 -1
- package/src/local/local-docs.ts +281 -2
- package/src/local/local-empty-workflows.ts +92 -0
- package/src/local/local-env.ts +16 -1
- package/src/local/local-file-checks.ts +104 -0
- package/src/local/local-function-ids.ts +141 -0
- package/src/local/local-function-recovery.ts +141 -0
- package/src/local/local-function-state.ts +87 -0
- package/src/local/local-keys.ts +7 -5
- package/src/local/local-knowledge-ingestion.ts +225 -0
- package/src/local/local-knowledge-uploads.ts +217 -0
- package/src/local/local-media-placeholders.ts +182 -0
- package/src/local/local-media-sets.ts +90 -10
- package/src/local/local-model-parameters.ts +89 -0
- package/src/local/local-object-sets.ts +18 -6
- package/src/local/local-operating.ts +35 -1
- package/src/local/local-org-settings.ts +205 -7
- package/src/local/local-orphans.ts +321 -0
- package/src/local/local-personas.ts +296 -0
- package/src/local/local-portability.ts +141 -16
- package/src/local/local-ports.ts +99 -7
- package/src/local/local-project-notices.ts +234 -0
- package/src/local/local-project-secrets.ts +133 -0
- package/src/local/local-project.ts +12 -0
- package/src/local/local-provider-refusal.ts +66 -0
- package/src/local/local-request-evidence.ts +112 -0
- package/src/local/local-runtime-health.ts +123 -0
- package/src/local/local-runtime-history.ts +50 -0
- package/src/local/local-runtime-target.ts +42 -0
- package/src/local/local-sample-data.ts +141 -0
- package/src/local/local-sample-remake.ts +138 -0
- package/src/local/local-scaffold.ts +336 -53
- package/src/local/local-schema-check.ts +148 -0
- package/src/local/local-schemas.ts +742 -25
- package/src/local/local-secret-carry.ts +107 -0
- package/src/local/local-secrets.ts +38 -8
- package/src/local/local-seed.ts +102 -26
- package/src/local/local-stand-ins.ts +1 -1
- package/src/local/local-state.ts +43 -2
- package/src/local/local-studio-server.ts +170 -7
- package/src/local/local-supervisor.ts +44 -6
- package/src/local/local-sync-names.ts +2 -0
- package/src/local/local-sync-plan.ts +11 -0
- package/src/local/local-sync-push.ts +10 -7
- package/src/local/local-sync-refs.ts +49 -3
- package/src/local/local-sync-remote.ts +343 -32
- package/src/local/local-sync-snapshot.ts +15 -10
- package/src/local/local-synthetic-data.ts +2116 -99
- package/src/local/local-test-baseline.ts +113 -0
- package/src/local/local-test-cases.ts +76 -1
- package/src/local/local-test-runner.ts +582 -91
- package/src/local/local-test-runs.ts +13 -5
- package/src/local/local-vendored-sdk.ts +183 -0
- package/src/local/local-workflow-state.ts +16 -3
- package/src/local/local-workflow-tests.ts +482 -0
- package/src/local/local-workspace-grants.ts +81 -0
- package/src/main.ts +50 -9
- package/src/mcp/a11y-scan.ts +55 -0
- package/src/mcp/browser/ax-snapshot.ts +181 -0
- package/src/mcp/browser/credentials.ts +78 -0
- package/src/mcp/browser/frames.ts +336 -0
- package/src/mcp/browser/headless-session.ts +413 -0
- package/src/mcp/browser/headless-tab.ts +575 -0
- package/src/mcp/browser/keys.ts +70 -0
- package/src/mcp/browser/page-scripts.ts +129 -0
- package/src/mcp/browser/wait.ts +73 -0
- package/src/mcp/forge-link.ts +126 -0
- package/src/mcp/headless.ts +199 -0
- package/src/mcp/mcp-apps.ts +435 -0
- package/src/mcp/mcp-chat.ts +226 -0
- package/src/mcp/mcp-cli.ts +53 -0
- package/src/mcp/mcp-protocol.ts +122 -0
- package/src/mcp/mcp-runtime.ts +113 -0
- package/src/mcp/mcp-server.ts +108 -0
- package/src/mcp/mcp-traces.ts +409 -0
- package/src/mcp/page-observation.ts +193 -0
- package/src/mcp/tools/agent-tools.ts +700 -0
- package/src/mcp/tools/app-tools.ts +241 -0
- package/src/mcp/tools/browser-tools.ts +310 -0
- package/src/mcp/tools/docs-tools.ts +32 -0
- package/src/mcp/tools/project-tools.ts +205 -0
- package/src/mcp/tools/test-tools.ts +339 -0
- package/src/mcp/tools/workflow-tools.ts +611 -0
- package/src/output.ts +1 -1
- package/src/pack.ts +8 -3
- package/src/packaging.ts +1 -1
- package/src/project-bootstrap.ts +107 -4
- package/src/project-context.ts +18 -1
- package/src/project.ts +27 -2
- package/src/provenance.ts +25 -0
- package/src/render-evidence.ts +65 -7
- package/src/template.ts +14 -8
- package/src/templates/next-app-files.ts +35 -6
- package/src/templates/next-skills.ts +61 -27
- package/src/vendor/kit-assets.json +6 -6
- package/src/vendor/sdk-sources.json +24 -21
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
3
|
+
|
|
4
|
+
import type { LocalProject } from './local-project'
|
|
5
|
+
import type { CaseResult } from './local-test-runner'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The outcome of each case at its last local run, so a run can say which
|
|
9
|
+
* cases its change made fail (newly failing) rather than leave the agent to
|
|
10
|
+
* compare two lists. Written by `frontera local test` and the MCP `test_run`,
|
|
11
|
+
* one entry per case, in the runtime's state folder.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export interface CaseOutcome {
|
|
15
|
+
passed: boolean
|
|
16
|
+
at: string
|
|
17
|
+
reason?: string
|
|
18
|
+
ms?: number
|
|
19
|
+
toolCalls?: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type LastRun = Record<string, CaseOutcome>
|
|
23
|
+
|
|
24
|
+
/** A case's key: its Agent, file and name. */
|
|
25
|
+
export function caseKey(r: Pick<CaseResult, 'agent' | 'file' | 'name'>): string {
|
|
26
|
+
return `${r.agent}\u0000${r.file}\u0000${r.name}`
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function lastRunPath(project: LocalProject): string {
|
|
30
|
+
return join(project.stateDir, 'test-results.json')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function readLastRun(project: LocalProject): LastRun {
|
|
34
|
+
const file = lastRunPath(project)
|
|
35
|
+
if (!existsSync(file)) return {}
|
|
36
|
+
try {
|
|
37
|
+
return JSON.parse(readFileSync(file, 'utf8')) as LastRun
|
|
38
|
+
} catch {
|
|
39
|
+
return {}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Keep these results as each case's last run; cases that did not run keep
|
|
45
|
+
* theirs, as does a case that couldn't run (the provider refused it, the
|
|
46
|
+
* runtime did not answer): it says nothing about the Agent.
|
|
47
|
+
*/
|
|
48
|
+
export function recordRun(project: LocalProject, results: CaseResult[], at = new Date().toISOString()): void {
|
|
49
|
+
const last = readLastRun(project)
|
|
50
|
+
for (const r of results) if (!r.notRun) last[caseKey(r)] = { passed: r.passed, at, ms: r.ms, ...(r.toolCalls !== undefined ? { toolCalls: r.toolCalls } : {}), ...(r.passed || !r.reason ? {} : { reason: r.reason.slice(0, 300) }) }
|
|
51
|
+
mkdirSync(project.stateDir, { recursive: true })
|
|
52
|
+
writeFileSync(lastRunPath(project), `${JSON.stringify(last, null, 2)}\n`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface RunComparison {
|
|
56
|
+
/** Passed at their last run and fail now: what the change broke. */
|
|
57
|
+
newlyFailing: CaseResult[]
|
|
58
|
+
/** Failed at their last run and pass now. */
|
|
59
|
+
fixed: CaseResult[]
|
|
60
|
+
/** Failed both times. */
|
|
61
|
+
stillFailing: CaseResult[]
|
|
62
|
+
/** No earlier run to compare with. */
|
|
63
|
+
firstRun: CaseResult[]
|
|
64
|
+
/**
|
|
65
|
+
* Couldn't run (the model provider refused them, the turn timed out, the
|
|
66
|
+
* runtime did not answer): neither newly failing nor still failing, whatever
|
|
67
|
+
* their last run was.
|
|
68
|
+
*/
|
|
69
|
+
couldNotRun: CaseResult[]
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function compareWithLastRun(results: CaseResult[], last: LastRun): RunComparison {
|
|
73
|
+
const out: RunComparison = { newlyFailing: [], fixed: [], stillFailing: [], firstRun: [], couldNotRun: [] }
|
|
74
|
+
for (const r of results) {
|
|
75
|
+
const before = last[caseKey(r)]
|
|
76
|
+
if (r.notRun) out.couldNotRun.push(r)
|
|
77
|
+
else if (!before) out.firstRun.push(r)
|
|
78
|
+
else if (before.passed && !r.passed) out.newlyFailing.push(r)
|
|
79
|
+
else if (!before.passed && r.passed) out.fixed.push(r)
|
|
80
|
+
else if (!before.passed && !r.passed) out.stillFailing.push(r)
|
|
81
|
+
}
|
|
82
|
+
return out
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Each case's run when its Agent was last applied to the workspace by a
|
|
87
|
+
* change request: the baseline a request's evals are compared with (design
|
|
88
|
+
* 2026-10-18 §6). Kept beside the last run; an Agent never applied from here
|
|
89
|
+
* has none, and its evals compare with the last run instead.
|
|
90
|
+
*/
|
|
91
|
+
function appliedRunPath(project: LocalProject): string {
|
|
92
|
+
return join(project.stateDir, 'test-results-applied.json')
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function readAppliedRun(project: LocalProject): LastRun {
|
|
96
|
+
const file = appliedRunPath(project)
|
|
97
|
+
if (!existsSync(file)) return {}
|
|
98
|
+
try {
|
|
99
|
+
return JSON.parse(readFileSync(file, 'utf8')) as LastRun
|
|
100
|
+
} catch {
|
|
101
|
+
return {}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** These Agents were applied: their cases' last runs become their applied baseline. */
|
|
106
|
+
export function recordApplied(project: LocalProject, agents: string[]): void {
|
|
107
|
+
if (!agents.length) return
|
|
108
|
+
const last = readLastRun(project)
|
|
109
|
+
const applied = readAppliedRun(project)
|
|
110
|
+
for (const [key, outcome] of Object.entries(last)) if (agents.includes(key.split('\u0000')[0]!)) applied[key] = outcome
|
|
111
|
+
mkdirSync(project.stateDir, { recursive: true })
|
|
112
|
+
writeFileSync(appliedRunPath(project), `${JSON.stringify(applied, null, 2)}\n`)
|
|
113
|
+
}
|
|
@@ -31,13 +31,45 @@ function block(text: string, indent: string): string {
|
|
|
31
31
|
return `|-\n${text.split('\n').map((line) => `${indent}${line}`).join('\n')}`
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
/** How a saved case answers the tool approvals its conversation stopped on: as the person did. */
|
|
35
|
+
export type SavedDecision = 'approve' | 'deny'
|
|
36
|
+
|
|
37
|
+
/** An approval card's status once someone approved it: approved, then run (executing, succeeded, failed, ambiguous). */
|
|
38
|
+
const APPROVED = new Set(['approved', 'executing', 'succeeded', 'failed', 'ambiguous', 'resumed'])
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* How the person decided the tool approvals a conversation stopped on, from
|
|
42
|
+
* the runtime's list of its cards (`/v1/chat/approvals?conversationId=`):
|
|
43
|
+
* `approve` when every decided card was approved, `deny` when every one was
|
|
44
|
+
* denied; nothing when none was decided (pending, expired) or the cards were
|
|
45
|
+
* decided both ways, which one case decision can't replay (`mixed`).
|
|
46
|
+
*/
|
|
47
|
+
export function decisionOfApprovals(cards: Array<{ status?: unknown; pauseReason?: unknown }>): { decision?: SavedDecision; mixed?: true } {
|
|
48
|
+
const decided = cards
|
|
49
|
+
.filter((c) => !c.pauseReason || c.pauseReason === 'needs_approval')
|
|
50
|
+
.map((c) => (typeof c.status === 'string' && APPROVED.has(c.status) ? 'approve' : c.status === 'denied' ? 'deny' : null))
|
|
51
|
+
.filter((d): d is SavedDecision => d !== null)
|
|
52
|
+
const kinds = new Set(decided)
|
|
53
|
+
if (kinds.size > 1) return { mixed: true }
|
|
54
|
+
return kinds.size ? { decision: [...kinds][0]! } : {}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** What saving a case says when its conversation's cards were decided both ways. */
|
|
58
|
+
export const MIXED_DECISIONS_LINE = 'the conversation approved some tool approvals and denied others; a case answers every one the same way, so it was saved without `decision`: add `decision: approve` or `deny` by hand, or split it into one case per turn'
|
|
59
|
+
|
|
60
|
+
export function renderTestCase(args: { conversationId: string; messages: ChatMessage[]; savedAt: string; variables?: Record<string, string | number | boolean>; decision?: SavedDecision }): string {
|
|
35
61
|
const turns = args.messages.filter((m) => (m.role === 'user' || m.role === 'assistant') && textOf(m))
|
|
36
62
|
const lastReply = [...turns].reverse().find((m) => m.role === 'assistant')
|
|
37
63
|
const inputs = turns.slice(0, lastReply ? turns.lastIndexOf(lastReply) : turns.length)
|
|
38
64
|
const lines = [
|
|
39
65
|
`- name: conversation-${args.conversationId.slice(0, 8)}`,
|
|
40
66
|
` savedFrom: studio ${args.savedAt}`,
|
|
67
|
+
// The tool approvals it stopped on, answered as the person did, so it replays to the end.
|
|
68
|
+
...(args.decision ? [` decision: ${args.decision}`] : []),
|
|
69
|
+
// The variables the conversation started with, so the case replays as the same customer.
|
|
70
|
+
...(args.variables && Object.keys(args.variables).length
|
|
71
|
+
? [' variables:', ...Object.entries(args.variables).map(([k, v]) => ` ${JSON.stringify(k)}: ${JSON.stringify(v)}`)]
|
|
72
|
+
: []),
|
|
41
73
|
' conversation:',
|
|
42
74
|
...inputs.map((m) => ` - ${m.role}: ${block(textOf(m), ' ')}`),
|
|
43
75
|
` expected: ${block(lastReply ? textOf(lastReply) : '', ' ')}`,
|
|
@@ -56,3 +88,46 @@ export function appendTestCase(root: string, agentSlug: string, yaml: string): s
|
|
|
56
88
|
appendFileSync(file, yaml.replace(/^/gm, ' ').replace(/ +$/gm, ''))
|
|
57
89
|
return relative(root, file)
|
|
58
90
|
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* The variables a conversation's Test setup recorded (`variables` on its
|
|
94
|
+
* `TRY_SETUP` session event, when the runtime records them), as a case file
|
|
95
|
+
* holds them: plain values only.
|
|
96
|
+
*/
|
|
97
|
+
export function variablesOfTimeline(timeline: Array<{ type?: string; payload?: Record<string, unknown> | null }>): Record<string, string | number | boolean> | undefined {
|
|
98
|
+
const setup = timeline.find((e) => e.payload?.event === 'TRY_SETUP')
|
|
99
|
+
const variables = setup?.payload?.variables
|
|
100
|
+
return variables && typeof variables === 'object' && !Array.isArray(variables) ? plainVariables(variables as Record<string, unknown>) : undefined
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** A conversation's variables a case can carry (strings, numbers, booleans), or undefined when none are. */
|
|
104
|
+
export function plainVariables(raw: Record<string, unknown> | undefined): Record<string, string | number | boolean> | undefined {
|
|
105
|
+
if (!raw) return undefined
|
|
106
|
+
const plain = Object.fromEntries(Object.entries(raw).filter((e): e is [string, string | number | boolean] => ['string', 'number', 'boolean'].includes(typeof e[1])))
|
|
107
|
+
return Object.keys(plain).length ? plain : undefined
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The sensitive variables a conversation's Test setup was given
|
|
112
|
+
* (`redactedVariables` on its `TRY_SETUP` session event): their names only,
|
|
113
|
+
* since the runtime never records their values. A case saved from the
|
|
114
|
+
* conversation lacks them until someone fills them in by hand.
|
|
115
|
+
*/
|
|
116
|
+
export function redactedVariablesOfTimeline(timeline: Array<{ type?: string; payload?: Record<string, unknown> | null }>): string[] {
|
|
117
|
+
const setup = timeline.find((e) => e.payload?.event === 'TRY_SETUP')
|
|
118
|
+
const keys = setup?.payload?.redactedVariables
|
|
119
|
+
return Array.isArray(keys) ? [...new Set(keys.filter((k): k is string => typeof k === 'string' && k !== ''))] : []
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** A case (one list item, as rendered) with a comment under its name naming the variables to fill in by hand. */
|
|
123
|
+
export function noteRedactedVariables(yaml: string, keys: string[]): string {
|
|
124
|
+
if (!keys.length) return yaml
|
|
125
|
+
const at = yaml.indexOf('\n')
|
|
126
|
+
const note = ` # Fill in by hand under variables: ${keys.join(', ')} (sensitive: the conversation's values are never recorded)`
|
|
127
|
+
return at < 0 ? `${yaml}\n${note}\n` : `${yaml.slice(0, at + 1)}${note}\n${yaml.slice(at + 1)}`
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** What saving a case says of the sensitive variables it could not keep. */
|
|
131
|
+
export function redactedVariablesLine(keys: string[], path: string): string {
|
|
132
|
+
return `not kept: ${keys.join(', ')} (sensitive: the Test setup records ${keys.length === 1 ? 'its name' : 'their names'}, never ${keys.length === 1 ? 'its value' : 'their values'}). Fill ${keys.length === 1 ? 'it' : 'them'} in by hand under the case's variables: in ${path}, or the case starts without ${keys.length === 1 ? 'it' : 'them'}.`
|
|
133
|
+
}
|