@pikku/cli 0.12.92 → 0.12.96
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/console-app/assets/{index-DDpIMCpy.js → index-CeQodYfg.js} +151 -149
- package/console-app/assets/{index-D0HG8q0B.css → index-CyYHTGWZ.css} +1 -1
- package/console-app/index.html +2 -2
- package/dist/.pikku/agent/pikku-agent-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.d.ts +1 -1
- package/dist/.pikku/channel/pikku-channel-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-channel.d.ts +13 -15
- package/dist/.pikku/cli/pikku-cli-channel.js +72 -3
- package/dist/.pikku/cli/pikku-cli-client.gen.d.ts +14 -4
- package/dist/.pikku/cli/pikku-cli-client.gen.js +20 -10
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-contracts-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-types.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings-meta.gen.json +156 -2
- package/dist/.pikku/cli/pikku-cli-wirings.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli-wirings.gen.js +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.d.ts +1 -1
- package/dist/.pikku/cli/pikku-cli.gen.js +1 -1
- package/dist/.pikku/console/pikku-node-types.gen.d.ts +1 -1
- package/dist/.pikku/function/pikku-function-types.gen.d.ts +55 -20
- package/dist/.pikku/function/pikku-function-types.gen.js +27 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.js +1 -1
- package/dist/.pikku/function/pikku-functions-meta.gen.json +176 -13
- package/dist/.pikku/function/pikku-functions.gen.js +11 -1
- package/dist/.pikku/http/pikku-http-types.gen.d.ts +1 -1
- package/dist/.pikku/http/pikku-http-types.gen.js +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.d.ts +1 -1
- package/dist/.pikku/mcp/pikku-mcp-types.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap-scenarios.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap-scenarios.gen.js +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.d.ts +1 -1
- package/dist/.pikku/pikku-bootstrap.gen.js +1 -1
- package/dist/.pikku/pikku-meta-service.gen.d.ts +1 -1
- package/dist/.pikku/pikku-meta-service.gen.js +1 -1
- package/dist/.pikku/pikku-services.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.d.ts +1 -1
- package/dist/.pikku/pikku-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-types.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings-meta.gen.js +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.d.ts +1 -1
- package/dist/.pikku/queue/pikku-queue-workers-wirings.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.js +1 -1
- package/dist/.pikku/rpc/pikku-rpc-wirings-meta.internal.gen.json +9 -0
- package/dist/.pikku/scenarios/pikku-scenario-functions-meta.gen.js +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-functions.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-wirings-meta.gen.js +1 -1
- package/dist/.pikku/scenarios/pikku-scenario-wirings.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/schemas/register.gen.d.ts +1 -1
- package/dist/.pikku/scenarios/schemas/register.gen.js +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.d.ts +1 -1
- package/dist/.pikku/scheduler/pikku-scheduler-types.gen.js +1 -1
- package/dist/.pikku/schemas/register.gen.js +21 -3
- package/dist/.pikku/schemas/schemas/KnowledgeIndexInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/KnowledgeIndexOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/KnowledgeValidateInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/KnowledgeValidateOutput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PersonaRunInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PersonaSyncInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuCLIConfig.schema.json +1 -1
- package/dist/.pikku/schemas/schemas/PikkuPersonasInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/PikkuRolesInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/RolesAuditInput.schema.json +1 -0
- package/dist/.pikku/schemas/schemas/RolesPruneInput.schema.json +1 -0
- package/dist/.pikku/scopes/pikku-personas-meta.gen.json +1 -0
- package/dist/.pikku/scopes/pikku-personas.gen.d.ts +36 -0
- package/dist/.pikku/scopes/pikku-personas.gen.js +17 -0
- package/dist/.pikku/scopes/pikku-roles-meta.gen.json +1 -0
- package/dist/.pikku/scopes/pikku-roles.gen.d.ts +30 -0
- package/dist/.pikku/scopes/pikku-roles.gen.js +18 -0
- package/dist/.pikku/scopes/pikku-scope-types.gen.d.ts +4 -2
- package/dist/.pikku/scopes/pikku-scope-types.gen.js +3 -2
- package/dist/.pikku/scopes/pikku-scopes.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secret-types.gen.d.ts +2 -2
- package/dist/.pikku/secrets/pikku-secret-types.gen.js +2 -2
- package/dist/.pikku/secrets/pikku-secrets.gen.d.ts +1 -1
- package/dist/.pikku/secrets/pikku-secrets.gen.js +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.d.ts +1 -1
- package/dist/.pikku/trigger/pikku-trigger-types.gen.js +1 -1
- package/dist/.pikku/variables/pikku-variable-types.gen.d.ts +2 -2
- package/dist/.pikku/variables/pikku-variable-types.gen.js +2 -2
- package/dist/.pikku/variables/pikku-variables.gen.d.ts +1 -1
- package/dist/.pikku/variables/pikku-variables.gen.js +1 -1
- package/dist/.pikku/workflow/meta/allWorkflow.gen.json +27 -3
- package/dist/.pikku/workflow/pikku-personas.gen.d.ts +15 -0
- package/dist/.pikku/workflow/pikku-personas.gen.js +17 -0
- package/dist/.pikku/workflow/pikku-workflow-types.gen.d.ts +96 -19
- package/dist/.pikku/workflow/pikku-workflow-types.gen.js +22 -2
- package/dist/.pikku/workflow/pikku-workflow-wirings-meta.gen.js +1 -1
- package/dist/.pikku/workflow/pikku-workflow-wirings.gen.js +1 -1
- package/dist/bin/pikku-bin.mjs +2 -2
- package/dist/bin/pikku.js +0 -0
- package/dist/src/cli.wiring.js +124 -2
- package/dist/src/deploy/analyzer/analyzer.js +14 -0
- package/dist/src/deploy/analyzer/manifest.d.ts +11 -0
- package/dist/src/fabric/functions/validate.function.js +20 -11
- package/dist/src/functions/commands/bootstrap.js +3 -0
- package/dist/src/functions/commands/dev-ai-runner.d.ts +16 -4
- package/dist/src/functions/commands/dev-ai-runner.js +63 -29
- package/dist/src/functions/commands/dev.js +9 -4
- package/dist/src/functions/commands/enable.js +15 -19
- package/dist/src/functions/commands/{scenario-environment.d.ts → environment.d.ts} +9 -6
- package/dist/src/functions/commands/{scenario-environment.js → environment.js} +5 -4
- package/dist/src/functions/commands/knowledge-index.d.ts +30 -0
- package/dist/src/functions/commands/knowledge-index.js +11 -0
- package/dist/src/functions/commands/knowledge-validate.d.ts +33 -0
- package/dist/src/functions/commands/knowledge-validate.js +11 -0
- package/dist/src/functions/commands/new-addon.d.ts +18 -0
- package/dist/src/functions/commands/new-addon.js +52 -36
- package/dist/src/functions/commands/persona-sync.d.ts +33 -0
- package/dist/src/functions/commands/persona-sync.js +151 -0
- package/dist/src/functions/commands/persona.d.ts +58 -0
- package/dist/src/functions/commands/persona.js +217 -0
- package/dist/src/functions/commands/roles-audit.d.ts +1 -0
- package/dist/src/functions/commands/roles-audit.js +37 -0
- package/dist/src/functions/commands/roles-prune.d.ts +16 -0
- package/dist/src/functions/commands/roles-prune.js +52 -0
- package/dist/src/functions/commands/roles-shared.d.ts +40 -0
- package/dist/src/functions/commands/roles-shared.js +77 -0
- package/dist/src/functions/commands/scenario-browser.d.ts +3 -3
- package/dist/src/functions/commands/scenario-browser.js +1 -1
- package/dist/src/functions/commands/scenario-ladder.d.ts +44 -2
- package/dist/src/functions/commands/scenario-ladder.js +51 -2
- package/dist/src/functions/commands/scenario.d.ts +7 -0
- package/dist/src/functions/commands/scenario.js +79 -17
- package/dist/src/functions/commands/scopes-shared.d.ts +8 -0
- package/dist/src/functions/commands/scopes-shared.js +1 -1
- package/dist/src/functions/commands/serve.js +7 -3
- package/dist/src/functions/commands/virtual-user-formatter.d.ts +30 -0
- package/dist/src/functions/commands/virtual-user-formatter.js +65 -0
- package/dist/src/functions/db/local-db.js +38 -1
- package/dist/src/functions/knowledge/render.d.ts +3 -0
- package/dist/src/functions/knowledge/render.js +66 -0
- package/dist/src/functions/knowledge/schemas.d.ts +51 -0
- package/dist/src/functions/knowledge/schemas.js +25 -0
- package/dist/src/functions/validate/workspace-validate.js +1 -1
- package/dist/src/functions/wirings/ai-agent/pikku-command-public-agent.js +2 -1
- package/dist/src/functions/wirings/ai-agent/serialize-public-agent.js +1 -0
- package/dist/src/functions/wirings/auth/serialize-auth-gen.d.ts +2 -2
- package/dist/src/functions/wirings/auth/serialize-auth-gen.js +8 -8
- package/dist/src/functions/wirings/cli/serialize-channel-cli-client.d.ts +7 -3
- package/dist/src/functions/wirings/cli/serialize-channel-cli-client.js +32 -18
- package/dist/src/functions/wirings/cli/serialize-channel-cli.js +63 -6
- package/dist/src/functions/wirings/console/serialize-console-functions.js +4 -7
- package/dist/src/functions/wirings/credentials/serialize-credentials-types.js +8 -1
- package/dist/src/functions/wirings/functions/serialize-function-types.js +88 -28
- package/dist/src/functions/wirings/middleware/serialize-middleware-imports.js +25 -9
- package/dist/src/functions/wirings/package/serialize-package-types.js +5 -3
- package/dist/src/functions/wirings/personas/pikku-command-personas.d.ts +7 -0
- package/dist/src/functions/wirings/personas/pikku-command-personas.js +43 -0
- package/dist/src/functions/wirings/personas/serialize-personas-types.d.ts +18 -0
- package/dist/src/functions/wirings/personas/serialize-personas-types.js +76 -0
- package/dist/src/functions/wirings/realtime/pikku-command-events-scaffold.js +2 -1
- package/dist/src/functions/wirings/roles/pikku-command-roles.d.ts +7 -0
- package/dist/src/functions/wirings/roles/pikku-command-roles.js +33 -0
- package/dist/src/functions/wirings/roles/serialize-roles-types.d.ts +14 -0
- package/dist/src/functions/wirings/roles/serialize-roles-types.js +55 -0
- package/dist/src/functions/wirings/rpc/pikku-command-public-rpc.js +2 -1
- package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.d.ts +3 -3
- package/dist/src/functions/wirings/scenarios/register-scenario-instrumentation.js +3 -3
- package/dist/src/functions/wirings/scopes/serialize-scopes-types.d.ts +1 -1
- package/dist/src/functions/wirings/scopes/serialize-scopes-types.js +1 -1
- package/dist/src/functions/wirings/secrets/serialize-secrets-types.js +10 -3
- package/dist/src/functions/wirings/user-admin/pikku-command-user-admin-functions.js +2 -1
- package/dist/src/functions/wirings/user-admin/serialize-user-admin-functions.js +2 -2
- package/dist/src/functions/wirings/variables/serialize-variables-types.js +8 -1
- package/dist/src/functions/wirings/workflow/pikku-command-workflow-routes.js +2 -1
- package/dist/src/functions/wirings/workflow/pikku-command-workflow.js +13 -13
- package/dist/src/functions/wirings/workflow/serialize-personas.d.ts +10 -0
- package/dist/src/functions/wirings/workflow/serialize-personas.js +46 -0
- package/dist/src/functions/wirings/workflow/serialize-scenario-step-map.js +11 -2
- package/dist/src/functions/wirings/workflow/serialize-workflow-types.js +165 -29
- package/dist/src/functions/workflows/all.workflow.js +7 -1
- package/dist/src/server/content-signing-jwt.d.ts +13 -0
- package/dist/src/server/content-signing-jwt.js +51 -0
- package/dist/src/server/dev-server-runner.interface.d.ts +10 -2
- package/dist/src/server/node-server-runner.d.ts +2 -2
- package/dist/src/server/node-server-runner.js +2 -1
- package/dist/src/services.d.ts +3 -1
- package/dist/src/services.js +2 -1
- package/dist/src/utils/pikku-cli-config.js +45 -2
- package/dist/src/utils/resolve-personas.d.ts +32 -0
- package/dist/src/utils/resolve-personas.js +46 -0
- package/dist/src/utils/resolve-scaffold-feature.d.ts +19 -0
- package/dist/src/utils/resolve-scaffold-feature.js +49 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +17 -13
- package/dist/.pikku/schemas/schemas/ScenarioRunInput.schema.json +0 -1
- package/dist/.pikku/workflow/pikku-scenario-actors.gen.d.ts +0 -19
- package/dist/.pikku/workflow/pikku-scenario-actors.gen.js +0 -17
- package/dist/src/functions/wirings/workflow/serialize-scenario-actors.d.ts +0 -2
- package/dist/src/functions/wirings/workflow/serialize-scenario-actors.js +0 -40
- package/dist/src/utils/resolve-scenario-actors.d.ts +0 -23
- package/dist/src/utils/resolve-scenario-actors.js +0 -74
|
@@ -1,18 +1,21 @@
|
|
|
1
1
|
import { resolve, join } from 'node:path';
|
|
2
2
|
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { pikkuSessionlessFunc } from '#pikku';
|
|
4
|
-
import { InMemoryWorkflowService
|
|
4
|
+
import { InMemoryWorkflowService } from '@pikku/core/services';
|
|
5
|
+
import { createHttpPersonas } from '@pikku/core/persona';
|
|
5
6
|
import { PikkuScenarioService } from '@pikku/core/scenario';
|
|
6
7
|
import { pikkuState, getAllPackageStates } from '@pikku/core/internal';
|
|
7
8
|
import { resolveFeatureScenarios } from '@pikku/core/workflow';
|
|
8
9
|
import { loadScenarioBootstrap } from './load-user-project.js';
|
|
9
|
-
import { collectScenarioStepProse, scenarioBrowserSteps, scenarioFailureFromSteps, scenarioStepRows, } from './scenario-ladder.js';
|
|
10
|
+
import { collectScenarioStepProse, scenarioBrowserSteps, scenarioFailureFromSteps, scenarioStepRows, scenarioStepsWithoutBinding, scenarioSurfaceCoverage, } from './scenario-ladder.js';
|
|
11
|
+
import { SCENARIO_SURFACES } from '@pikku/core/workflow';
|
|
10
12
|
import { formatScenarioReport } from './scenario-formatter.js';
|
|
11
13
|
import { resolveScenarioBrowserProvider, scenarioBrowserLifecycle, } from './scenario-browser.js';
|
|
12
|
-
import {
|
|
14
|
+
import { resolvePersonas } from '../../utils/resolve-personas.js';
|
|
13
15
|
import { spawnDevServer } from '../../server/spawn-dev-server.js';
|
|
14
16
|
import { buildScenarioPlan } from './scenario-plan.js';
|
|
15
|
-
import {
|
|
17
|
+
import { resolveEnvironment } from './environment.js';
|
|
18
|
+
import { createDevAIAgentRunner } from './dev-ai-runner.js';
|
|
16
19
|
const isScenario = (wf) => wf?.scenario === true;
|
|
17
20
|
const listScenarios = (state) => Object.entries(state.workflows?.meta ?? {})
|
|
18
21
|
.filter(([, wf]) => isScenario(wf))
|
|
@@ -84,14 +87,17 @@ export const scenarioList = pikkuSessionlessFunc({
|
|
|
84
87
|
},
|
|
85
88
|
});
|
|
86
89
|
export const scenarioRun = pikkuSessionlessFunc({
|
|
87
|
-
func: async ({ logger, config, getInspectorState, variables }, { environment, flows, features, tags, excludeTags, coverage, browser = true, spawn = false, keepAlive = false, trace = false, apiUrl, appUrl, }) => {
|
|
90
|
+
func: async ({ logger, config, getInspectorState, variables }, { environment, flows, features, tags, excludeTags, coverage, browser = true, run: runSurface = 'default', strict = false, spawn = false, keepAlive = false, trace = false, apiUrl, appUrl, }) => {
|
|
91
|
+
if (!SCENARIO_SURFACES.includes(runSurface)) {
|
|
92
|
+
throw new Error(`Unknown --run surface '${runSurface}'. Expected one of: ${SCENARIO_SURFACES.join(', ')}.`);
|
|
93
|
+
}
|
|
88
94
|
const state = await getInspectorState(true, false, false, true);
|
|
89
95
|
// Resolved once, so actors, step env, the browser driver and any spawned
|
|
90
96
|
// server all target the same place — including when the target only exists
|
|
91
97
|
// at run time and arrives through --api-url/--app-url.
|
|
92
|
-
const env =
|
|
98
|
+
const env = resolveEnvironment({
|
|
93
99
|
environment,
|
|
94
|
-
environments: config.
|
|
100
|
+
environments: config.environments ?? {},
|
|
95
101
|
apiUrl,
|
|
96
102
|
appUrl,
|
|
97
103
|
spawn,
|
|
@@ -164,16 +170,17 @@ export const scenarioRun = pikkuSessionlessFunc({
|
|
|
164
170
|
throw new Error('SCENARIO_ACTOR_SECRET is not set — scenario actors cannot sign in. ' +
|
|
165
171
|
'Export it in the environment running this command (never put it in pikku.config.json).');
|
|
166
172
|
}
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
// registry, and codegen resolved the same way to type `
|
|
170
|
-
const scenarioActors =
|
|
171
|
-
const actors =
|
|
173
|
+
// Every declared persona, with its address filled in. Resolved once: the
|
|
174
|
+
// HTTP personas and the Playwright provider below must see the same
|
|
175
|
+
// registry, and codegen resolved the same way to type `PersonaName`.
|
|
176
|
+
const scenarioActors = resolvePersonas(state.personas?.definitions ?? [], config.scenarios?.emailDomain);
|
|
177
|
+
const actors = createHttpPersonas({
|
|
172
178
|
apiUrl: env.apiUrl,
|
|
173
179
|
secret,
|
|
174
|
-
|
|
180
|
+
personas: scenarioActors,
|
|
175
181
|
signInPath: env.signInPath,
|
|
176
182
|
rpcPath: env.rpcPath,
|
|
183
|
+
model: config.scenarios?.model,
|
|
177
184
|
});
|
|
178
185
|
const functionsMeta = state.functions?.meta ?? {};
|
|
179
186
|
const scenarioNames = new Set(groups.flatMap((group) => group.entries.map((e) => e.scenarioName)));
|
|
@@ -183,6 +190,15 @@ export const scenarioRun = pikkuSessionlessFunc({
|
|
|
183
190
|
scenarioBrowserSteps(state.workflows?.meta?.[name], functionsMeta),
|
|
184
191
|
])
|
|
185
192
|
.filter(([, steps]) => steps.length > 0));
|
|
193
|
+
// `--no-browser` is the blunt form of `--run default`: a machine with no
|
|
194
|
+
// browser takes the server-side path rather than skipping the scenario.
|
|
195
|
+
const effectiveSurface = browser === false && runSurface === 'browser' ? 'default' : runSurface;
|
|
196
|
+
const unrunnableStepsByFlow = new Map([...scenarioNames]
|
|
197
|
+
.map((name) => [
|
|
198
|
+
name,
|
|
199
|
+
scenarioStepsWithoutBinding(state.workflows?.meta?.[name], functionsMeta, effectiveSurface),
|
|
200
|
+
])
|
|
201
|
+
.filter(([, steps]) => steps.length > 0));
|
|
186
202
|
// Two reasons a scenario is held back, both reported as SKIP rather than
|
|
187
203
|
// failed. `--no-browser` is the direct replacement for cucumber's `@console`
|
|
188
204
|
// tag, so a standard run stays green on a machine with no browser; a `skip`
|
|
@@ -191,8 +207,9 @@ export const scenarioRun = pikkuSessionlessFunc({
|
|
|
191
207
|
const skipReasonFor = (entry) => {
|
|
192
208
|
if (entry.skip)
|
|
193
209
|
return entry.skip;
|
|
194
|
-
|
|
195
|
-
|
|
210
|
+
const unrunnable = unrunnableStepsByFlow.get(entry.scenarioName);
|
|
211
|
+
if (unrunnable?.length) {
|
|
212
|
+
return `no ${effectiveSurface} or default binding: ${unrunnable.join(', ')}`;
|
|
196
213
|
}
|
|
197
214
|
return undefined;
|
|
198
215
|
};
|
|
@@ -215,10 +232,27 @@ export const scenarioRun = pikkuSessionlessFunc({
|
|
|
215
232
|
apiUrl: env.apiUrl,
|
|
216
233
|
appUrl: env.appUrl,
|
|
217
234
|
});
|
|
235
|
+
scenarioService.setRunSurface(effectiveSurface);
|
|
236
|
+
// Scenario steps run here, not on the target — everything they touch of the
|
|
237
|
+
// app goes over HTTP through an actor, which is what `guardRpc` below
|
|
238
|
+
// enforces. `actor.converse` is the exception, and the reason this is not
|
|
239
|
+
// just the three services above: the persona's own turns are LLM calls made
|
|
240
|
+
// in this process, and without a runner every conversing scenario fails
|
|
241
|
+
// before it says anything. Built the same way `pikku dev` builds its own,
|
|
242
|
+
// and only when the project declares agents, so a project with no agents
|
|
243
|
+
// does not have to have AI env set to run scenarios.
|
|
244
|
+
const aiAgentRunner = Object.keys(state.agents?.agentsMeta ?? {}).length > 0
|
|
245
|
+
? await createDevAIAgentRunner({
|
|
246
|
+
logger,
|
|
247
|
+
projectRoot: config.rootDir,
|
|
248
|
+
variables,
|
|
249
|
+
})
|
|
250
|
+
: undefined;
|
|
218
251
|
pikkuState(null, 'package', 'singletonServices', {
|
|
219
252
|
logger,
|
|
220
253
|
workflowService,
|
|
221
254
|
workflowRunService: workflowService,
|
|
255
|
+
...(aiAgentRunner ? { aiAgentRunner } : {}),
|
|
222
256
|
});
|
|
223
257
|
const guardRpc = {
|
|
224
258
|
rpcWithWire: async (rpcName) => {
|
|
@@ -227,7 +261,11 @@ export const scenarioRun = pikkuSessionlessFunc({
|
|
|
227
261
|
`'${environment}' (${env.apiUrl}), not local services.`);
|
|
228
262
|
},
|
|
229
263
|
};
|
|
230
|
-
|
|
264
|
+
// Only a browser run launches one. Under `--run default` a step with a
|
|
265
|
+
// browser binding takes its default path instead, so there is nothing to
|
|
266
|
+
// drive and nothing to pay for.
|
|
267
|
+
const needsBrowser = effectiveSurface === 'browser' &&
|
|
268
|
+
groups.some((group) => group.entries.some((entry) => browserStepsByFlow.has(entry.scenarioName)));
|
|
231
269
|
const failureDir = join(resolve(config.rootDir, config.outDir), 'scenario-failures');
|
|
232
270
|
const browserLifecycle = scenarioBrowserLifecycle(needsBrowser
|
|
233
271
|
? await (async () => {
|
|
@@ -448,8 +486,32 @@ export const scenarioRun = pikkuSessionlessFunc({
|
|
|
448
486
|
})) {
|
|
449
487
|
logger[level](text);
|
|
450
488
|
}
|
|
489
|
+
// How much of the run actually happened on the surface it targeted. Every
|
|
490
|
+
// step counts, so a step that fell back to the server lowers the ratio
|
|
491
|
+
// rather than needing a footnote. Assertions that fell back are named
|
|
492
|
+
// separately — those are sentences claiming an observation nobody made.
|
|
493
|
+
const surfaceCoverage = { onSurface: 0, total: 0 };
|
|
494
|
+
const unwitnessed = new Set();
|
|
495
|
+
for (const name of scenarioNames) {
|
|
496
|
+
const scenario = scenarioSurfaceCoverage(state.workflows?.meta?.[name], functionsMeta, effectiveSurface);
|
|
497
|
+
surfaceCoverage.onSurface += scenario.onSurface;
|
|
498
|
+
surfaceCoverage.total += scenario.total;
|
|
499
|
+
for (const step of scenario.unwitnessed)
|
|
500
|
+
unwitnessed.add(step);
|
|
501
|
+
}
|
|
502
|
+
if (effectiveSurface !== 'default' && surfaceCoverage.total > 0) {
|
|
503
|
+
const line = `${surfaceCoverage.onSurface}/${surfaceCoverage.total} steps ran on ${effectiveSurface}`;
|
|
504
|
+
if (unwitnessed.size === 0) {
|
|
505
|
+
logger.info(line);
|
|
506
|
+
}
|
|
507
|
+
else {
|
|
508
|
+
logger[strict ? 'error' : 'warn'](`${line} — asserted server-side only: ${[...unwitnessed].join(', ')}`);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
451
511
|
const failed = results.filter((r) => r.status === 'failed');
|
|
452
|
-
if (failed.length > 0 ||
|
|
512
|
+
if (failed.length > 0 ||
|
|
513
|
+
hookFailures.length > 0 ||
|
|
514
|
+
(strict && unwitnessed.size > 0)) {
|
|
453
515
|
process.exitCode = 1;
|
|
454
516
|
}
|
|
455
517
|
},
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import type { Kysely } from 'kysely';
|
|
1
2
|
import type { FlatScope } from '@pikku/core/scope';
|
|
2
3
|
import { KyselyScopeService } from '@pikku/kysely';
|
|
4
|
+
import type { KyselyPikkuDB } from '@pikku/kysely';
|
|
3
5
|
type Logger = {
|
|
4
6
|
info: (msg: string) => void;
|
|
5
7
|
warn: (msg: string) => void;
|
|
@@ -8,6 +10,12 @@ type Logger = {
|
|
|
8
10
|
export declare const loadDeclaredScopes: (scopesMetaJsonFile: string, logger: Logger) => Promise<FlatScope[] | null>;
|
|
9
11
|
export type OpenedScopeService = {
|
|
10
12
|
service: KyselyScopeService;
|
|
13
|
+
/**
|
|
14
|
+
* The same connection the service holds, for the one thing it cannot answer:
|
|
15
|
+
* a user's id from their address. Grants are keyed by id, and the `user`
|
|
16
|
+
* table those ids come from is better-auth's, not the scope schema's.
|
|
17
|
+
*/
|
|
18
|
+
db: Kysely<KyselyPikkuDB>;
|
|
11
19
|
destroy: () => Promise<void>;
|
|
12
20
|
};
|
|
13
21
|
/**
|
|
@@ -62,7 +62,7 @@ export const openScopeService = async ({ config, logger, }, declared, command) =
|
|
|
62
62
|
const service = new KyselyScopeService(db);
|
|
63
63
|
await service.init();
|
|
64
64
|
await service.syncScopes(declared);
|
|
65
|
-
return { service, destroy: () => db.destroy() };
|
|
65
|
+
return { service, db, destroy: () => db.destroy() };
|
|
66
66
|
}
|
|
67
67
|
catch (e) {
|
|
68
68
|
await db.destroy();
|
|
@@ -12,6 +12,7 @@ import { loadUserBootstrap, loadUserModule } from './load-user-project.js';
|
|
|
12
12
|
import { createDevAIAgentRunner } from './dev-ai-runner.js';
|
|
13
13
|
import { resolveConsoleMount } from './serve-console.js';
|
|
14
14
|
import { serverReadyLine } from '../../server/server-ready.js';
|
|
15
|
+
import { createEphemeralContentSigningJWT } from '../../server/content-signing-jwt.js';
|
|
15
16
|
export const serve = pikkuSessionlessFunc({
|
|
16
17
|
remote: true,
|
|
17
18
|
func: async ({ logger, config, getInspectorState, variables, devServerRunner }, { port, console: serveConsole }) => {
|
|
@@ -54,8 +55,11 @@ export const serve = pikkuSessionlessFunc({
|
|
|
54
55
|
sizeLimit: userConfig.content.sizeLimit,
|
|
55
56
|
}
|
|
56
57
|
: undefined;
|
|
57
|
-
const
|
|
58
|
-
?
|
|
58
|
+
const contentSigningJWT = localContentConfig
|
|
59
|
+
? createEphemeralContentSigningJWT()
|
|
60
|
+
: undefined;
|
|
61
|
+
const localContent = localContentConfig && contentSigningJWT
|
|
62
|
+
? new LocalContent(localContentConfig, logger, contentSigningJWT)
|
|
59
63
|
: undefined;
|
|
60
64
|
const schedulerService = new InMemorySchedulerService();
|
|
61
65
|
const aiStorage = kysely
|
|
@@ -128,7 +132,7 @@ export const serve = pikkuSessionlessFunc({
|
|
|
128
132
|
port: resolvedPort,
|
|
129
133
|
content: localContentConfig,
|
|
130
134
|
...(consoleMount ? { staticMounts: [consoleMount] } : {}),
|
|
131
|
-
}, logger);
|
|
135
|
+
}, logger, { contentSigningJWT });
|
|
132
136
|
const lifecycle = await loadLifecycle();
|
|
133
137
|
await pikkuServer.init();
|
|
134
138
|
await lifecycle?.beforeStart?.(resolvedServices);
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The virtual user run report, and how it is rendered.
|
|
3
|
+
*
|
|
4
|
+
* A run is not a pass/fail: a virtual user pursuing plausible goals against a
|
|
5
|
+
* real stage produces a story — what it tried, what it gave up on, and what
|
|
6
|
+
* came back that should not have. The findings are the product; everything else
|
|
7
|
+
* is context for reading them.
|
|
8
|
+
*
|
|
9
|
+
* Kept a pure function over plain data for the same reason the scenario
|
|
10
|
+
* reporter is: a second formatter (JSON for fabric, JUnit for CI) is then one
|
|
11
|
+
* function rather than an unpicking of the runner.
|
|
12
|
+
*/
|
|
13
|
+
import type { VirtualUserRunResult } from '@pikku/core/virtual-user';
|
|
14
|
+
export interface VirtualUserReportContext {
|
|
15
|
+
persona: string;
|
|
16
|
+
disposition: string;
|
|
17
|
+
environment: string;
|
|
18
|
+
apiUrl: string;
|
|
19
|
+
/** How much of the catalogue's read/write split had to be guessed at. */
|
|
20
|
+
catalogue: {
|
|
21
|
+
total: number;
|
|
22
|
+
annotated: number;
|
|
23
|
+
inferred: number;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export interface VirtualUserReportLine {
|
|
27
|
+
level: 'info' | 'error' | 'warn';
|
|
28
|
+
text: string;
|
|
29
|
+
}
|
|
30
|
+
export declare const formatVirtualUserReport: (result: VirtualUserRunResult, context: VirtualUserReportContext) => VirtualUserReportLine[];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const STATUS_MARK = {
|
|
2
|
+
completed: 'DONE',
|
|
3
|
+
abandoned: 'DROP',
|
|
4
|
+
stuck: 'STUCK',
|
|
5
|
+
suspended: 'OPEN',
|
|
6
|
+
open: 'OPEN',
|
|
7
|
+
};
|
|
8
|
+
/** Why a run ended, in words rather than an enum. */
|
|
9
|
+
const STOPPED_BY = {
|
|
10
|
+
'no-intents': 'this persona had nothing to want — no scenario names them',
|
|
11
|
+
'budget-steps': 'ran out of steps',
|
|
12
|
+
'budget-mutations': 'ran out of mutations',
|
|
13
|
+
'budget-duration': 'ran out of time',
|
|
14
|
+
'stop-hook': 'the app called a halt',
|
|
15
|
+
exhausted: 'saw everything it came for through',
|
|
16
|
+
};
|
|
17
|
+
const findingLine = (finding) => ` ${finding.kind.padEnd(18)} ${finding.detail} (step ${finding.step})`;
|
|
18
|
+
const plural = (count, noun) => `${count} ${noun}${count === 1 ? '' : 's'}`;
|
|
19
|
+
export const formatVirtualUserReport = (result, context) => {
|
|
20
|
+
const lines = [];
|
|
21
|
+
const info = (text) => lines.push({ level: 'info', text });
|
|
22
|
+
const error = (text) => lines.push({ level: 'error', text });
|
|
23
|
+
const warn = (text) => lines.push({ level: 'warn', text });
|
|
24
|
+
info(`Virtual user '${context.persona}' (${context.disposition}) against '${context.environment}' — ${context.apiUrl}`);
|
|
25
|
+
info(` seed ${result.seed} · ${plural(context.catalogue.total, 'endpoint')} offered` +
|
|
26
|
+
(context.catalogue.inferred
|
|
27
|
+
? ` · read/write guessed for ${context.catalogue.inferred} of them`
|
|
28
|
+
: ''));
|
|
29
|
+
if (result.intents.length) {
|
|
30
|
+
info('');
|
|
31
|
+
info('What it tried');
|
|
32
|
+
for (const intent of result.intents) {
|
|
33
|
+
const mark = STATUS_MARK[intent.status] ?? intent.status.toUpperCase();
|
|
34
|
+
const interrupted = intent.suspensions
|
|
35
|
+
? `, put down ${plural(intent.suspensions, 'time')}`
|
|
36
|
+
: '';
|
|
37
|
+
const summary = intent.summary ? ` — ${intent.summary}` : '';
|
|
38
|
+
info(` ${mark.padEnd(5)} ${intent.title} (${plural(intent.steps.length, 'step')}${interrupted})${summary}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
info('');
|
|
42
|
+
if (result.findings.length === 0) {
|
|
43
|
+
info('Nothing came back that should not have.');
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
error(`${plural(result.findings.length, 'finding')}`);
|
|
47
|
+
for (const finding of result.findings) {
|
|
48
|
+
error(findingLine(finding));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const { tally } = result;
|
|
52
|
+
info('');
|
|
53
|
+
info(`${plural(tally.steps, 'step')} · ${plural(tally.calls, 'call')} · ` +
|
|
54
|
+
`${plural(tally.mutations, 'mutation')} · ${tally.tokensIn}/${tally.tokensOut} tokens ` +
|
|
55
|
+
`(${tally.model}) · ${Math.round(tally.elapsedMs / 1000)}s`);
|
|
56
|
+
info(`Stopped because it ${STOPPED_BY[result.stoppedBy]}.`);
|
|
57
|
+
if (result.stoppedBy === 'no-intents') {
|
|
58
|
+
warn(`Give it something to want: declare a scenario naming '${context.persona}', or pass --goals.`);
|
|
59
|
+
}
|
|
60
|
+
// A seed is only worth printing when there is something to reproduce.
|
|
61
|
+
if (result.findings.length) {
|
|
62
|
+
info(`Replay this exact run with --seed ${result.seed}.`);
|
|
63
|
+
}
|
|
64
|
+
return lines;
|
|
65
|
+
};
|
|
@@ -123,13 +123,50 @@ async function createPostgresClient(resolved) {
|
|
|
123
123
|
const db = await createEmbeddedPostgres(resolved.pgliteDir);
|
|
124
124
|
return pgliteAsClient(db);
|
|
125
125
|
}
|
|
126
|
+
let embeddedPostgresWasm;
|
|
127
|
+
/**
|
|
128
|
+
* Compile PGlite's two WASM modules once and hand them to every instance.
|
|
129
|
+
*
|
|
130
|
+
* PGlite already caches them across instances internally, so this saves one
|
|
131
|
+
* 10.5MB compile per process rather than one per instance — measured at ~5% off
|
|
132
|
+
* a four-instance run. It is worth the few lines because a `WebAssembly.Module`
|
|
133
|
+
* is stateless compiled code, safe to instantiate any number of times.
|
|
134
|
+
*
|
|
135
|
+
* Note what this does *not* fix: each instance still compiles ~4MB of its own
|
|
136
|
+
* WASM (~3.3MB of it the pgcrypto side module, which PGlite offers no way to
|
|
137
|
+
* share) across ~15 module constructions, and frees it on close. That per
|
|
138
|
+
* instance churn is the suspected trigger for the rare V8 abort seen in CI on
|
|
139
|
+
* Linux x64, where PKU-based ThreadIsolation loses track of a code allocation:
|
|
140
|
+
*
|
|
141
|
+
* Check failed: jit_page_->allocations_.erase(addr) == 1.
|
|
142
|
+
*
|
|
143
|
+
* Resolution mirrors PGlite's own `new URL('./pglite.wasm', import.meta.url)`.
|
|
144
|
+
* If the files are not where the package puts them (a bundled CLI, say), we
|
|
145
|
+
* hand back nothing and PGlite loads them itself as before.
|
|
146
|
+
*/
|
|
147
|
+
async function loadEmbeddedPostgresWasm() {
|
|
148
|
+
try {
|
|
149
|
+
const dist = dirname(createRequire(import.meta.url).resolve('@electric-sql/pglite'));
|
|
150
|
+
const [pgliteWasmModule, initdbWasmModule] = await Promise.all([
|
|
151
|
+
WebAssembly.compile(readFileSync(join(dist, 'pglite.wasm'))),
|
|
152
|
+
WebAssembly.compile(readFileSync(join(dist, 'initdb.wasm'))),
|
|
153
|
+
]);
|
|
154
|
+
return { pgliteWasmModule, initdbWasmModule };
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return undefined;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
126
160
|
async function createEmbeddedPostgres(dataDir) {
|
|
127
|
-
|
|
161
|
+
embeddedPostgresWasm ??= loadEmbeddedPostgresWasm();
|
|
162
|
+
const [{ PGlite }, { pgcrypto }, wasm] = await Promise.all([
|
|
128
163
|
import('@electric-sql/pglite'),
|
|
129
164
|
import('@electric-sql/pglite/contrib/pgcrypto'),
|
|
165
|
+
embeddedPostgresWasm,
|
|
130
166
|
]);
|
|
131
167
|
return new PGlite({
|
|
132
168
|
...(dataDir ? { dataDir } : {}),
|
|
169
|
+
...wasm,
|
|
133
170
|
extensions: {
|
|
134
171
|
pgcrypto,
|
|
135
172
|
},
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { KnowledgeIndexResult, KnowledgeValidateResult } from '@pikku/knowledge';
|
|
2
|
+
export declare const renderKnowledgeValidate: (_services: unknown, { ok, notes, findings }: KnowledgeValidateResult) => void;
|
|
3
|
+
export declare const renderKnowledgeIndex: (_services: unknown, { ok, check, files }: KnowledgeIndexResult) => void;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { added, changed, dim, removed } from '../../fabric/lib/output.js';
|
|
2
|
+
export const renderKnowledgeValidate = (_services, { ok, notes, findings }) => {
|
|
3
|
+
const errors = findings.filter((f) => f.severity === 'error');
|
|
4
|
+
const warns = findings.filter((f) => f.severity === 'warn');
|
|
5
|
+
const infos = findings.filter((f) => f.severity === 'info');
|
|
6
|
+
for (const finding of [...errors, ...warns, ...infos]) {
|
|
7
|
+
const icon = finding.severity === 'error'
|
|
8
|
+
? removed('✗')
|
|
9
|
+
: finding.severity === 'warn'
|
|
10
|
+
? changed('⚠')
|
|
11
|
+
: dim('ℹ');
|
|
12
|
+
console.log(`${icon} ${finding.message}`);
|
|
13
|
+
console.log(` ${dim('fix:')} ${finding.fixHint}`);
|
|
14
|
+
console.log();
|
|
15
|
+
}
|
|
16
|
+
const counts = [dim(`${notes} note${notes !== 1 ? 's' : ''}`)];
|
|
17
|
+
if (errors.length) {
|
|
18
|
+
counts.push(removed(`${errors.length} error${errors.length !== 1 ? 's' : ''}`));
|
|
19
|
+
}
|
|
20
|
+
if (warns.length) {
|
|
21
|
+
counts.push(changed(`${warns.length} warning${warns.length !== 1 ? 's' : ''}`));
|
|
22
|
+
}
|
|
23
|
+
console.log('─'.repeat(40));
|
|
24
|
+
console.log(counts.join(' '));
|
|
25
|
+
if (ok && warns.length === 0 && errors.length === 0) {
|
|
26
|
+
console.log(added('✓') + ' ' + dim('the knowledge base is consistent'));
|
|
27
|
+
}
|
|
28
|
+
// An error means the base contradicts itself, which a pipeline has to be able
|
|
29
|
+
// to stop on — so this command reports it the only way a shell can read.
|
|
30
|
+
if (!ok)
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
};
|
|
33
|
+
export const renderKnowledgeIndex = (_services, { ok, check, files }) => {
|
|
34
|
+
if (files.length === 0) {
|
|
35
|
+
console.log(dim('No knowledge notes — nothing to index.'));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
for (const file of files) {
|
|
39
|
+
if (file.action === 'unchanged') {
|
|
40
|
+
console.log(`${dim('=')} ${dim(file.path)}`);
|
|
41
|
+
}
|
|
42
|
+
else if (check) {
|
|
43
|
+
console.log(`${removed('✗')} ${file.path} ${dim(`(${file.action})`)}`);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.log(`${added('✓')} ${file.path} ${dim(`(${file.action})`)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
console.log('─'.repeat(40));
|
|
50
|
+
if (!check) {
|
|
51
|
+
const written = files.filter((f) => f.action !== 'unchanged').length;
|
|
52
|
+
console.log(written === 0
|
|
53
|
+
? added('✓') + ' ' + dim('every index was already current')
|
|
54
|
+
: added('✓') +
|
|
55
|
+
` ${written} index file${written !== 1 ? 's' : ''} written`);
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
console.log(ok
|
|
59
|
+
? added('✓') + ' ' + dim('every index is current')
|
|
60
|
+
: removed('✗') +
|
|
61
|
+
' ' +
|
|
62
|
+
'indexes are stale — run `pikku knowledge index`');
|
|
63
|
+
if (!ok)
|
|
64
|
+
process.exitCode = 1;
|
|
65
|
+
}
|
|
66
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The knowledge command schemas, re-bound to variables declared HERE rather than
|
|
3
|
+
* imported straight into the command files.
|
|
4
|
+
*
|
|
5
|
+
* Two things about how the CLI builds itself make this the only shape that works:
|
|
6
|
+
*
|
|
7
|
+
* 1. The build bootstraps with the PUBLISHED CLI and inspector, so the code that
|
|
8
|
+
* reads these schemas is whatever npm has, not what is in this worktree. An
|
|
9
|
+
* identifier imported from `@pikku/knowledge` resolves to that package's
|
|
10
|
+
* `.d.ts`, and importing a declaration file yields a module with no exports at
|
|
11
|
+
* all — every one of them is a type. A binding declared in a `.ts` gives the
|
|
12
|
+
* inspector a file it can execute, and the import inside it resolves to the
|
|
13
|
+
* package's emitted JS.
|
|
14
|
+
* 2. The command files import `#pikku`, which points at `dist/.pikku/` — output
|
|
15
|
+
* that does not exist yet while the bootstrap is running. A schema the
|
|
16
|
+
* inspector has to import therefore cannot live beside a wired function.
|
|
17
|
+
*
|
|
18
|
+
* `schemaRuntimeFile` in the inspector fixes (1) at the root, so once a release
|
|
19
|
+
* carrying it is the bootstrap version, (2) is the only reason this file is here.
|
|
20
|
+
*/
|
|
21
|
+
export declare const KnowledgeIndexInputSchema: import("zod").ZodObject<{
|
|
22
|
+
check: import("zod").ZodOptional<import("zod").ZodBoolean>;
|
|
23
|
+
}, import("zod/v4/core").$strip>;
|
|
24
|
+
export declare const KnowledgeIndexOutputSchema: import("zod").ZodObject<{
|
|
25
|
+
ok: import("zod").ZodBoolean;
|
|
26
|
+
check: import("zod").ZodBoolean;
|
|
27
|
+
files: import("zod").ZodArray<import("zod").ZodObject<{
|
|
28
|
+
path: import("zod").ZodString;
|
|
29
|
+
action: import("zod").ZodEnum<{
|
|
30
|
+
created: "created";
|
|
31
|
+
updated: "updated";
|
|
32
|
+
unchanged: "unchanged";
|
|
33
|
+
}>;
|
|
34
|
+
}, import("zod/v4/core").$strip>>;
|
|
35
|
+
}, import("zod/v4/core").$strip>;
|
|
36
|
+
export declare const KnowledgeValidateInputSchema: import("zod").ZodObject<{}, import("zod/v4/core").$strip>;
|
|
37
|
+
export declare const KnowledgeValidateOutputSchema: import("zod").ZodObject<{
|
|
38
|
+
ok: import("zod").ZodBoolean;
|
|
39
|
+
notes: import("zod").ZodNumber;
|
|
40
|
+
findings: import("zod").ZodArray<import("zod").ZodObject<{
|
|
41
|
+
id: import("zod").ZodString;
|
|
42
|
+
severity: import("zod").ZodEnum<{
|
|
43
|
+
error: "error";
|
|
44
|
+
warn: "warn";
|
|
45
|
+
info: "info";
|
|
46
|
+
}>;
|
|
47
|
+
message: import("zod").ZodString;
|
|
48
|
+
path: import("zod").ZodString;
|
|
49
|
+
fixHint: import("zod").ZodString;
|
|
50
|
+
}, import("zod/v4/core").$strip>>;
|
|
51
|
+
}, import("zod/v4/core").$strip>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { KnowledgeIndexInput, KnowledgeIndexOutput, KnowledgeValidateInput, KnowledgeValidateOutput, } from '@pikku/knowledge';
|
|
2
|
+
/**
|
|
3
|
+
* The knowledge command schemas, re-bound to variables declared HERE rather than
|
|
4
|
+
* imported straight into the command files.
|
|
5
|
+
*
|
|
6
|
+
* Two things about how the CLI builds itself make this the only shape that works:
|
|
7
|
+
*
|
|
8
|
+
* 1. The build bootstraps with the PUBLISHED CLI and inspector, so the code that
|
|
9
|
+
* reads these schemas is whatever npm has, not what is in this worktree. An
|
|
10
|
+
* identifier imported from `@pikku/knowledge` resolves to that package's
|
|
11
|
+
* `.d.ts`, and importing a declaration file yields a module with no exports at
|
|
12
|
+
* all — every one of them is a type. A binding declared in a `.ts` gives the
|
|
13
|
+
* inspector a file it can execute, and the import inside it resolves to the
|
|
14
|
+
* package's emitted JS.
|
|
15
|
+
* 2. The command files import `#pikku`, which points at `dist/.pikku/` — output
|
|
16
|
+
* that does not exist yet while the bootstrap is running. A schema the
|
|
17
|
+
* inspector has to import therefore cannot live beside a wired function.
|
|
18
|
+
*
|
|
19
|
+
* `schemaRuntimeFile` in the inspector fixes (1) at the root, so once a release
|
|
20
|
+
* carrying it is the bootstrap version, (2) is the only reason this file is here.
|
|
21
|
+
*/
|
|
22
|
+
export const KnowledgeIndexInputSchema = KnowledgeIndexInput;
|
|
23
|
+
export const KnowledgeIndexOutputSchema = KnowledgeIndexOutput;
|
|
24
|
+
export const KnowledgeValidateInputSchema = KnowledgeValidateInput;
|
|
25
|
+
export const KnowledgeValidateOutputSchema = KnowledgeValidateOutput;
|
|
@@ -97,7 +97,7 @@ export async function runWorkspaceValidate(startDir = process.cwd()) {
|
|
|
97
97
|
}
|
|
98
98
|
const scaffold = pikkuConfig.scaffold;
|
|
99
99
|
if (!scaffold?.console) {
|
|
100
|
-
e('pikku-config-no-console-scaffold', 'pikku.config.json missing "scaffold.console" — Fabric cannot introspect the running app (console:getFunctionsMeta and friends 404), so the sandbox builder shows no functions', pikkuConfigPath, 'Add "console":
|
|
100
|
+
e('pikku-config-no-console-scaffold', 'pikku.config.json missing "scaffold.console" — Fabric cannot introspect the running app (console:getFunctionsMeta and friends 404), so the sandbox builder shows no functions', pikkuConfigPath, 'Add "console": true under "scaffold" in pikku.config.json — the console requires an authenticated session, so Better Auth must be set up (see the pikku-better-auth skill)');
|
|
101
101
|
}
|
|
102
102
|
}
|
|
103
103
|
const rootPkgPath = join(root, 'package.json');
|
|
@@ -4,11 +4,12 @@ import { writeFileInDir } from '../../../utils/file-writer.js';
|
|
|
4
4
|
import { logCommandInfoAndTime } from '../../../middleware/log-command-info-and-time.js';
|
|
5
5
|
import { removeLegacyScaffoldFile } from '../../../utils/remove-legacy-scaffold-file.js';
|
|
6
6
|
import { serializePublicAgent } from './serialize-public-agent.js';
|
|
7
|
+
import { resolveScaffoldFeature } from '../../../utils/resolve-scaffold-feature.js';
|
|
7
8
|
export const pikkuPublicAgent = pikkuSessionlessFunc({
|
|
8
9
|
func: async ({ logger, config }) => {
|
|
9
10
|
if (config.scaffold?.agent && config.publicAgentSchemasFile) {
|
|
10
11
|
const pathToPikkuTypes = getFileImportRelativePath(config.publicAgentFile, config.typesDeclarationFile, config.packageMappings);
|
|
11
|
-
const { schemas, functions } = serializePublicAgent(pathToPikkuTypes, config.scaffold.agent
|
|
12
|
+
const { schemas, functions } = serializePublicAgent(pathToPikkuTypes, resolveScaffoldFeature('agent', config.scaffold.agent).auth, config.globalHTTPPrefix || '');
|
|
12
13
|
await writeFileInDir(logger, config.publicAgentSchemasFile, schemas);
|
|
13
14
|
await writeFileInDir(logger, config.publicAgentFile, functions);
|
|
14
15
|
await removeLegacyScaffoldFile(config.publicAgentFile);
|
|
@@ -81,6 +81,7 @@ export const isThreadOwner = pikkuPermission<{ threadId: string }>(
|
|
|
81
81
|
)
|
|
82
82
|
|
|
83
83
|
export const agentCaller = pikkuSessionlessFunc({
|
|
84
|
+
description: 'Run a named AI agent with a message and return its reply',
|
|
84
85
|
tags: ['pikku'],
|
|
85
86
|
auth: ${authFlag},
|
|
86
87
|
input: AgentCall,
|
|
@@ -5,14 +5,14 @@ import type { AuthDefinition } from '@pikku/inspector';
|
|
|
5
5
|
* They are split because the CLI's schema/wiring-separation rule (PKU490)
|
|
6
6
|
* forbids a file from declaring Zod schemas AND `wireHTTPRoutes` together —
|
|
7
7
|
* schema files are imported at runtime, which would fire HTTP wiring
|
|
8
|
-
* side-effects without a server context. `
|
|
8
|
+
* side-effects without a server context. `defineSecret`/`defineVariable` are NOT
|
|
9
9
|
* HTTP wiring, so schemas may sit alongside them; only the route wiring must be
|
|
10
10
|
* separated out.
|
|
11
11
|
*/
|
|
12
12
|
export interface AuthGenOutput {
|
|
13
13
|
/** Handler + catch-all routes (+ session middleware, unless split out). */
|
|
14
14
|
wiring: string;
|
|
15
|
-
/** The secrets file: Zod schemas +
|
|
15
|
+
/** The secrets file: Zod schemas + defineSecret/defineVariable. */
|
|
16
16
|
secrets: string;
|
|
17
17
|
/** Global session-middleware registration, split into its own file only when
|
|
18
18
|
* `session.cookieCache` is enabled (stateless path); undefined otherwise. */
|
|
@@ -59,22 +59,22 @@ export const serializeAuthGen = (definition, providers, authFile, typesDeclarati
|
|
|
59
59
|
// the package's `imports` map (e.g. a project's `pikku/` dir), where `#pikku`
|
|
60
60
|
// would not resolve. This mirrors how the console/agent scaffolds import it.
|
|
61
61
|
const pikkuTypesImportPath = getFileImportRelativePath(authFile, typesDeclarationFile, packageMappings);
|
|
62
|
-
// ─── Secrets file: Zod schemas +
|
|
62
|
+
// ─── Secrets file: Zod schemas + defineSecret/defineVariable ──────────────────
|
|
63
63
|
const secrets = [
|
|
64
64
|
'// AUTO-GENERATED by pikku CLI — do not edit',
|
|
65
65
|
'',
|
|
66
|
-
`import {
|
|
66
|
+
`import { defineSecret } from '@pikku/core/secret'`,
|
|
67
67
|
];
|
|
68
68
|
const hasVariables = known.some((name) => PROVIDER_REGISTRY[name].variables);
|
|
69
69
|
if (hasVariables) {
|
|
70
|
-
secrets.push(`import {
|
|
70
|
+
secrets.push(`import { defineVariable } from '@pikku/core/variable'`);
|
|
71
71
|
}
|
|
72
72
|
secrets.push(`import { z } from 'zod'`, '');
|
|
73
73
|
// better-auth's session-signing secret is always required (its BETTER_AUTH_SECRET
|
|
74
74
|
// env convention). Wire it so the platform collects it, regardless of providers.
|
|
75
75
|
secrets.push(`export const BetterAuthSecretSchema = z.string()`);
|
|
76
76
|
secrets.push('');
|
|
77
|
-
secrets.push(`
|
|
77
|
+
secrets.push(`defineSecret({`);
|
|
78
78
|
secrets.push(` name: 'betterAuthSecret',`);
|
|
79
79
|
secrets.push(` displayName: 'Better Auth Secret',`);
|
|
80
80
|
secrets.push(` description: 'Signing secret for better-auth sessions',`);
|
|
@@ -92,12 +92,12 @@ export const serializeAuthGen = (definition, providers, authFile, typesDeclarati
|
|
|
92
92
|
secrets.push(`})`);
|
|
93
93
|
secrets.push('');
|
|
94
94
|
}
|
|
95
|
-
//
|
|
95
|
+
// defineSecret for each provider.
|
|
96
96
|
for (const name of known) {
|
|
97
97
|
const def = PROVIDER_REGISTRY[name];
|
|
98
98
|
const schemaName = providerSchemaName(name);
|
|
99
99
|
const secretName = providerSecretName(name);
|
|
100
|
-
secrets.push(`
|
|
100
|
+
secrets.push(`defineSecret({`);
|
|
101
101
|
secrets.push(` name: '${secretName}',`);
|
|
102
102
|
secrets.push(` displayName: '${def.displayName}',`);
|
|
103
103
|
secrets.push(` secretId: '${def.secretId}',`);
|
|
@@ -105,7 +105,7 @@ export const serializeAuthGen = (definition, providers, authFile, typesDeclarati
|
|
|
105
105
|
secrets.push(`})`);
|
|
106
106
|
secrets.push('');
|
|
107
107
|
}
|
|
108
|
-
//
|
|
108
|
+
// defineVariable for providers with non-secret config (issuer, tenantId, etc.).
|
|
109
109
|
for (const name of known) {
|
|
110
110
|
const def = PROVIDER_REGISTRY[name];
|
|
111
111
|
if (!def.variables)
|
|
@@ -116,7 +116,7 @@ export const serializeAuthGen = (definition, providers, authFile, typesDeclarati
|
|
|
116
116
|
const schemaName = variableSchemaName(name, field);
|
|
117
117
|
secrets.push(`export const ${schemaName} = z.string()`);
|
|
118
118
|
secrets.push('');
|
|
119
|
-
secrets.push(`
|
|
119
|
+
secrets.push(`defineVariable({`);
|
|
120
120
|
secrets.push(` name: '${name}_${field}',`);
|
|
121
121
|
secrets.push(` displayName: '${def.displayName} ${capitalize(field)}',`);
|
|
122
122
|
secrets.push(` description: '${meta.description}',`);
|