@inneranimalmedia/agentsam-sdk 2.6.3 → 2.6.4
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/AGENTSAM.md +6 -0
- package/README.md +16 -1
- package/bin/agentsam +16 -1
- package/docs/BRAND_INTELLIGENCE.md +1 -1
- package/docs/architecture/AGENTSAM_DISTRIBUTION_OWNERSHIP.md +176 -0
- package/docs/architecture/AGENTSAM_GO_RUNTIME.md +282 -0
- package/docs/architecture/CODEBASEINDEX_GUIDED_PATH.md +202 -0
- package/docs/architecture/FS_E2E_CLOSURE_RECEIPT.md +59 -0
- package/docs/architecture/LOCAL_FS_AUTHORITY.md +38 -0
- package/docs/architecture/LOCAL_STUDIO_CLOUDFLARE_OAUTH.md +28 -0
- package/docs/architecture/PLAN_CLI_AND_LOCAL_STUDIO_DESKTOP.md +96 -0
- package/docs/architecture/SAM_ACTIVITY_RECOVERY_RECEIPT.md +39 -0
- package/docs/architecture/SAM_DECISION_WORK_RECEIPT.md +54 -0
- package/docs/architecture/SAM_KERNEL.md +181 -0
- package/docs/architecture/SAM_MACHINE_NORMALIZATION_PRECOMMIT_REPORT.md +208 -0
- package/docs/architecture/SLASH_SKILLS_PORTABLE.md +116 -0
- package/docs/architecture/fs-e2e-receipt.latest.json +42 -0
- package/docs/architecture/previews/codebaseindex-guided-path/CODEBASEINDEX_GUIDED_PATH.md +202 -0
- package/docs/architecture/previews/codebaseindex-guided-path/index.html +321 -0
- package/migrations/d1/0010_portable_tickets_memory.sql +140 -0
- package/migrations/d1/0011_agentsam_skill_v2.sql +185 -0
- package/migrations/d1/0011b_agentsam_skill_v2_cutover.sql +22 -0
- package/migrations/d1/0011c_agentsam_skill_v2_backfill.sql +76 -0
- package/migrations/d1/0011d_agentsam_skill_v2_retrieval_revisions.sql +50 -0
- package/migrations/d1/0012_agentsam_tools_required_seed.sql +67 -0
- package/migrations/d1/0013_identity_oauth_states.sql +15 -0
- package/migrations/d1/0014_auth_event_log.sql +20 -0
- package/migrations/d1/0015_identity_oauth_state_app_id.sql +3 -0
- package/migrations/d1/README_PORTABLE_CONTROL_PLANE.md +16 -0
- package/migrations/sqlite/agentsam_skill_retrieval.portable.sql +42 -0
- package/package.json +19 -4
- package/packages/agentsam-contracts/src/errors.ts +16 -0
- package/packages/agentsam-errors/src/envelope.js +53 -0
- package/packages/agentsam-errors/src/index.js +1 -0
- package/packages/agentsam-errors/src/recovery.js +301 -0
- package/packages/agentsam-knowledge/src/providers/index.js +18 -6
- package/packages/connectors/cloudflare/src/routes.js +9 -0
- package/packages/connectors/cloudflare/tests/connector.test.mjs +26 -1
- package/packages/identity/.agentsam/features/oauth-login-portal/agentsam.feature.json +1 -1
- package/packages/identity/.agentsam/features/oauth-login-portal/routes.json +11 -2
- package/packages/identity/docs/PORTABLE_IDENTITY_ARCHITECTURE.md +50 -0
- package/packages/identity/migrations/D1_SCHEMA_MAPPING.md +31 -0
- package/packages/identity/migrations/sqlite/001_identity_core.sql +99 -0
- package/packages/identity/migrations/sqlite/002_identity_oauth_client.sql +38 -0
- package/packages/identity/migrations/sqlite/003_identity_oauth_server.sql +64 -0
- package/packages/identity/package.json +2 -2
- package/packages/identity/src/adapters/cloudflare-d1/index.js +122 -22
- package/packages/identity/src/adapters/sqlite/index.js +319 -0
- package/packages/identity/src/app/verify-app.js +95 -0
- package/packages/identity/src/contracts/identity-store.js +115 -0
- package/packages/identity/src/contracts/route-ids.js +30 -0
- package/packages/identity/src/contracts/route-projection.js +218 -0
- package/packages/identity/src/contracts/routes.js +11 -0
- package/packages/identity/src/core/browser-paths.js +4 -5
- package/packages/identity/src/core/constants.js +13 -8
- package/packages/identity/src/core/session-policy.js +32 -0
- package/packages/identity/src/frontend/auth-portal/pages/login.html +10 -10
- package/packages/identity/src/frontend/auth-portal/pages/reset.html +3 -3
- package/packages/identity/src/frontend/auth-portal/pages/signup.html +3 -3
- package/packages/identity/src/frontend/auth-portal/preview/dashboard-stub.html +1 -1
- package/packages/identity/src/index.js +25 -0
- package/packages/identity/src/oauth/README.md +10 -4
- package/packages/identity/src/oauth/credentials.js +20 -13
- package/packages/identity/src/oauth/finalize-inbound.js +1 -1
- package/packages/identity/src/oauth/iam-platform.js +8 -7
- package/packages/identity/src/oauth/redirect-paths.js +27 -36
- package/packages/identity/src/server/identity-service.js +30 -13
- package/packages/identity/src/server/mount-policy.js +30 -0
- package/packages/identity/src/server/post-auth.js +79 -0
- package/packages/identity/src/server/worker-router.js +104 -72
- package/packages/identity/tests/finalize-inbound-oauth.test.mjs +6 -6
- package/packages/identity/tests/iam-provider.test.mjs +1 -1
- package/packages/identity/tests/identity-service.test.mjs +36 -5
- package/packages/identity/tests/oauth-credentials.test.mjs +3 -1
- package/packages/identity/tests/portable-identity-architecture.test.mjs +157 -0
- package/packages/identity/tests/session-routes-policy.test.mjs +21 -0
- package/packages/theme-church-site/package.json +2 -1
- package/packages/theme-church-site/src/index.js +1 -0
- package/packages/theme-companions-site/package.json +2 -1
- package/packages/theme-companions-site/src/index.js +1 -0
- package/packages/theme-floors-site/package.json +2 -1
- package/packages/theme-floors-site/src/index.js +1 -0
- package/packages/theme-fuelnfree-site/package.json +2 -1
- package/packages/theme-fuelnfree-site/src/index.js +1 -0
- package/packages/theme-handyman-site/package.json +2 -1
- package/packages/theme-handyman-site/src/index.js +1 -0
- package/packages/theme-insurance-site/package.json +2 -1
- package/packages/theme-insurance-site/src/index.js +1 -0
- package/packages/theme-shinshu-site/package.json +2 -1
- package/packages/theme-shinshu-site/src/index.js +1 -0
- package/protocol/apps/agentsam.app.v1.schema.json +51 -0
- package/protocol/brand/brandpack.v1.schema.json +43 -0
- package/protocol/credentials/issue.v1.schema.json +38 -0
- package/protocol/database/connection.v1.schema.json +41 -0
- package/protocol/embeddings/embedding-profile.v1.schema.json +20 -0
- package/protocol/errors/error-envelope.schema.json +135 -1
- package/protocol/errors/recovery.v1.schema.json +50 -0
- package/protocol/runtime/workspace-fs.v1.schema.json +71 -0
- package/protocol/sam/activity.v1.schema.json +48 -0
- package/protocol/sam/answer.v1.schema.json +35 -0
- package/protocol/sam/calibration.v1.schema.json +21 -0
- package/protocol/sam/decision-receipt.v1.schema.json +29 -0
- package/protocol/sam/evaluation.v1.schema.json +19 -0
- package/protocol/sam/operation.schema.json +66 -0
- package/protocol/sam/outcome.v1.schema.json +36 -0
- package/protocol/sam/question.v1.schema.json +26 -0
- package/protocol/sam/registry.seed.json +153 -0
- package/protocol/sam/result.schema.json +53 -0
- package/protocol/sam/state.v1.schema.json +23 -0
- package/protocol/skills/agentsam.interaction.v1.schema.json +50 -0
- package/protocol/skills/agentsam.skill.v1.schema.json +57 -0
- package/protocol/ui/icon-registry.mjs +226 -0
- package/protocol/ui/icon.v1.schema.json +51 -0
- package/skills/README.md +22 -9
- package/skills/agentsam-codebaseindex/SKILL.md +225 -0
- package/skills/catalog.json +14 -0
- package/src/cli/command-catalog.js +130 -0
- package/src/cli/dispatch.js +48 -0
- package/src/cli.js +43 -1
- package/src/commands/api-key.js +244 -0
- package/src/commands/app.js +60 -28
- package/src/commands/brand.js +17 -19
- package/src/commands/codebaseindex.js +688 -0
- package/src/commands/env.js +152 -25
- package/src/commands/go.js +366 -53
- package/src/commands/interaction-clack.js +115 -0
- package/src/commands/models.js +1 -1
- package/src/commands/providers.js +62 -14
- package/src/commands/shell.js +43 -2
- package/src/commands/skill.js +248 -0
- package/src/commands/skills.js +1 -1
- package/src/commands/start-local.js +4 -0
- package/src/commands/whoami.js +90 -18
- package/src/go/build.js +229 -39
- package/src/go/cloudflare.js +506 -105
- package/src/go/container.js +120 -0
- package/src/go/contract.js +9 -4
- package/src/go/discover.js +149 -33
- package/src/go/index.js +15 -3
- package/src/go/native-probe-runner.mjs +119 -0
- package/src/go/{registry.js → official-registry.js} +64 -7
- package/src/go/official-release.js +10 -0
- package/src/go/receipts.js +77 -10
- package/src/go/verify.js +9 -2
- package/src/index.js +27 -0
- package/src/indexing/ingest/discover-models.js +298 -0
- package/src/indexing/ingest/inventory.js +243 -0
- package/src/indexing/ingest/job-graph.js +181 -0
- package/src/indexing/ingest/materials.js +210 -0
- package/src/lib/provider-credentials.js +63 -21
- package/src/lib/slash-commands.js +1 -0
- package/src/local-fs/capability.js +121 -0
- package/src/local-fs/freshness.js +75 -0
- package/src/local-fs/index.js +385 -0
- package/src/local-fs/paths.js +100 -0
- package/src/local-pty/server.js +295 -19
- package/src/mcp/client.js +2 -2
- package/src/models/ai-access-onboarding.js +112 -0
- package/src/models/discovery.js +10 -2
- package/src/models/inventory-core.js +9 -1
- package/src/sam/activity/index.js +183 -0
- package/src/sam/client.js +252 -0
- package/src/sam/decision/calibration.js +109 -0
- package/src/sam/decision/confidence.js +126 -0
- package/src/sam/decision/evaluate.js +157 -0
- package/src/sam/decision/evaluators/deterministic.js +341 -0
- package/src/sam/decision/evaluators/heuristic.js +61 -0
- package/src/sam/decision/evaluators/select.js +50 -0
- package/src/sam/decision/evaluators/semantic.js +149 -0
- package/src/sam/decision/hierarchical.js +61 -0
- package/src/sam/decision/index.js +53 -0
- package/src/sam/decision/policy.js +86 -0
- package/src/sam/decision/questions.js +120 -0
- package/src/sam/decision/receipt.js +148 -0
- package/src/sam/decision/state.js +117 -0
- package/src/sam/decision/types.js +22 -0
- package/src/sam/decision/validate.js +200 -0
- package/src/sam/define.js +51 -0
- package/src/sam/index.js +65 -0
- package/src/sam/operations/brand-scan.js +62 -0
- package/src/sam/operations/cad-blender-inspect.js +36 -0
- package/src/sam/operations/codebaseindex-ingest.js +49 -0
- package/src/sam/operations/decision-evaluate.js +59 -0
- package/src/sam/operations/planning-astar.js +77 -0
- package/src/sam/operations/planning-goap.js +60 -0
- package/src/sam/operations/repository-inspect.js +72 -0
- package/src/sam/operations/security-scan.js +33 -0
- package/src/sam/operations/terminal-exec.js +29 -0
- package/src/sam/planning/astar.js +311 -0
- package/src/sam/planning/goap.js +177 -0
- package/src/sam/planning/index.js +21 -0
- package/src/sam/planning/state.js +84 -0
- package/src/sam/registry.js +48 -0
- package/src/sam/result.js +77 -0
- package/src/sam/seed.js +44 -0
- package/src/sam/types.js +91 -0
- package/src/skills/catalog.js +64 -0
- package/src/skills/content-resolver.js +124 -0
- package/src/skills/hosted-store.js +37 -0
- package/src/skills/index.js +29 -64
- package/src/skills/interaction.js +102 -0
- package/src/skills/local-store.js +228 -0
- package/src/skills/manifest.js +104 -0
- package/src/skills/metrics.js +31 -0
- package/src/skills/registry.js +184 -0
- package/src/skills/runtime.js +287 -0
- package/src/skills/slash.js +44 -0
- package/src/ui/cli/help.js +94 -101
- package/test/cli/api-key-env-whoami.test.mjs +129 -0
- package/test/cli/codebaseindex-plan-ux.test.mjs +37 -0
- package/test/cli/go.test.mjs +62 -5
- package/test/cli/skill-npm-and-env.test.mjs +52 -0
- package/test/cli/wireframes-go-registry.test.mjs +87 -2
- package/test/go/build-source-identity.test.mjs +31 -0
- package/test/go/cloudflare-probe.test.mjs +274 -10
- package/test/go/distribution.test.mjs +28 -0
- package/test/integration/ai-access-onboarding.test.mjs +47 -0
- package/test/integration/cli-help.test.mjs +1 -1
- package/test/integration/cms-site-tenancy-contract.test.mjs +6 -6
- package/test/integration/codebaseindex-ingest.test.mjs +166 -0
- package/test/integration/icon-registry.test.mjs +67 -0
- package/test/integration/ingest-discover-models.test.mjs +30 -0
- package/test/integration/install-script.test.mjs +12 -9
- package/test/integration/local-fs.test.mjs +113 -0
- package/test/integration/provider-env-cli.test.mjs +2 -1
- package/test/integration/sam-activity-recovery.test.mjs +147 -0
- package/test/integration/sam-decision.test.mjs +584 -0
- package/test/integration/sam-kernel.test.mjs +99 -0
- package/test/integration/sam-planning-astar.test.mjs +279 -0
- package/test/integration/skill-runtime.test.mjs +240 -0
- package/test/integration/studio-fs-pty-e2e.test.mjs +294 -0
- package/test/models.test.mjs +14 -7
- package/test/shell.test.mjs +4 -4
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-question batch evaluation.
|
|
3
|
+
*
|
|
4
|
+
* Questions in one batch:
|
|
5
|
+
* - see the same original state
|
|
6
|
+
* - are logically independent
|
|
7
|
+
* - do not consume another question's answer
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { buildDecisionState, hashDecisionState } from './state.js';
|
|
11
|
+
import { validateQuestionBatch } from './validate.js';
|
|
12
|
+
import { selectEvaluator, DEFAULT_CHAIN } from './evaluators/select.js';
|
|
13
|
+
import { applyDecisionPolicy } from './policy.js';
|
|
14
|
+
import { createDecisionReceipt } from './receipt.js';
|
|
15
|
+
import { DECISION_EVALUATION_SCHEMA } from './types.js';
|
|
16
|
+
import { getRegisteredQuestion } from './questions.js';
|
|
17
|
+
import { activityFromDecision } from '../activity/index.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {object} opts
|
|
21
|
+
* @param {object} opts.state
|
|
22
|
+
* @param {Array<object|string>} opts.questions question objects or registry ids
|
|
23
|
+
* @param {object[]} [opts.evaluators]
|
|
24
|
+
* @param {object} [opts.policy]
|
|
25
|
+
* @param {boolean} [opts.receipt]
|
|
26
|
+
*/
|
|
27
|
+
export async function evaluate(opts = {}) {
|
|
28
|
+
const state = buildDecisionState(opts.state || {});
|
|
29
|
+
const stateHash = hashDecisionState(state);
|
|
30
|
+
const questions = validateQuestionBatch(
|
|
31
|
+
(opts.questions || []).map((q) => (typeof q === 'string' ? getRegisteredQuestion(q) : q)),
|
|
32
|
+
);
|
|
33
|
+
const evaluators = opts.evaluators || DEFAULT_CHAIN;
|
|
34
|
+
|
|
35
|
+
// Group by selected evaluator — each question independently routed.
|
|
36
|
+
/** @type {Map<object, object[]>} */
|
|
37
|
+
const groups = new Map();
|
|
38
|
+
/** @type {Record<string, object>} */
|
|
39
|
+
const routing = {};
|
|
40
|
+
for (const q of questions) {
|
|
41
|
+
const ev = selectEvaluator(q, evaluators);
|
|
42
|
+
routing[q.id] = { kind: ev.kind, version: ev.version };
|
|
43
|
+
if (!groups.has(ev)) groups.set(ev, []);
|
|
44
|
+
groups.get(ev).push(q);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** @type {Record<string, object>} */
|
|
48
|
+
const answers = {};
|
|
49
|
+
/** @type {object[]} */
|
|
50
|
+
const usedEvaluators = [];
|
|
51
|
+
let modelUsed = false;
|
|
52
|
+
|
|
53
|
+
for (const [ev, qs] of groups) {
|
|
54
|
+
// Each group receives the SAME original state — never prior answers.
|
|
55
|
+
const result = await ev.evaluate(state, qs);
|
|
56
|
+
usedEvaluators.push(result.evaluator || { kind: ev.kind, version: ev.version });
|
|
57
|
+
if (result.model_used) modelUsed = true;
|
|
58
|
+
for (const [id, answer] of Object.entries(result.answers || {})) {
|
|
59
|
+
// Independence check: answer must not reference sibling answers.
|
|
60
|
+
if (answer && answer._consumed_answers) {
|
|
61
|
+
const err = new Error(`question_dependency_leak:${id}`);
|
|
62
|
+
err.code = 'question_independence_violated';
|
|
63
|
+
throw err;
|
|
64
|
+
}
|
|
65
|
+
answers[id] = answer;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Ensure every question answered
|
|
70
|
+
for (const q of questions) {
|
|
71
|
+
if (!answers[q.id]) {
|
|
72
|
+
const err = new Error(`missing_answer:${q.id}`);
|
|
73
|
+
err.code = 'evaluation_incomplete';
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const policy_result = applyDecisionPolicy({
|
|
79
|
+
answers,
|
|
80
|
+
policy: opts.policy || {},
|
|
81
|
+
state,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const evaluation = {
|
|
85
|
+
schema: DECISION_EVALUATION_SCHEMA,
|
|
86
|
+
ok: true,
|
|
87
|
+
state_hash: stateHash,
|
|
88
|
+
answers,
|
|
89
|
+
routing,
|
|
90
|
+
policy_result,
|
|
91
|
+
model_used: modelUsed,
|
|
92
|
+
deterministic: !modelUsed,
|
|
93
|
+
question_versions: Object.fromEntries(questions.map((q) => [q.id, q.version])),
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
if (opts.receipt !== false) {
|
|
97
|
+
evaluation.receipt = createDecisionReceipt({
|
|
98
|
+
state,
|
|
99
|
+
state_hash: stateHash,
|
|
100
|
+
answers,
|
|
101
|
+
policy_result,
|
|
102
|
+
question_ids: questions.map((q) => q.id),
|
|
103
|
+
question_versions: evaluation.question_versions,
|
|
104
|
+
evaluators: usedEvaluators,
|
|
105
|
+
evaluator: usedEvaluators[0] || null,
|
|
106
|
+
action: opts.action ?? null,
|
|
107
|
+
run_id: opts.run_id ?? null,
|
|
108
|
+
step_id: opts.step_id ?? null,
|
|
109
|
+
action_id: opts.action_id ?? null,
|
|
110
|
+
evidence: opts.evidence || [],
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Optional live activity fan-out (Studio / CLI share the same events)
|
|
115
|
+
if (opts.activity && typeof opts.activity.emit === 'function' && evaluation.receipt) {
|
|
116
|
+
for (const [qid, answer] of Object.entries(answers)) {
|
|
117
|
+
opts.activity.emit(
|
|
118
|
+
activityFromDecision({
|
|
119
|
+
run_id: opts.run_id || opts.activity.run_id,
|
|
120
|
+
step_id: opts.step_id,
|
|
121
|
+
decision_id: evaluation.receipt.decision_id,
|
|
122
|
+
question_id: qid,
|
|
123
|
+
answer,
|
|
124
|
+
phase: opts.activity_phase || 'plan',
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return evaluation;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Explicit two-stage evaluation: stage2 state must be newly built by caller.
|
|
135
|
+
* @param {object} stage1 evaluate() opts
|
|
136
|
+
* @param {(stage1Result: object) => object|Promise<object>} buildStage2State
|
|
137
|
+
* @param {object} stage2 { questions, policy?, evaluators? }
|
|
138
|
+
*/
|
|
139
|
+
export async function evaluateDependentStages(stage1, buildStage2State, stage2) {
|
|
140
|
+
const first = await evaluate(stage1);
|
|
141
|
+
if (typeof buildStage2State !== 'function') {
|
|
142
|
+
const err = new Error('dependent_stage_requires_explicit_state_builder');
|
|
143
|
+
err.code = 'invalid_dependent_stage';
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
const nextState = await buildStage2State(first);
|
|
147
|
+
if (!nextState || nextState === stage1.state) {
|
|
148
|
+
const err = new Error('dependent_stage_must_build_new_state');
|
|
149
|
+
err.code = 'invalid_dependent_stage';
|
|
150
|
+
throw err;
|
|
151
|
+
}
|
|
152
|
+
const second = await evaluate({
|
|
153
|
+
...stage2,
|
|
154
|
+
state: nextState,
|
|
155
|
+
});
|
|
156
|
+
return { stage1: first, stage2: second };
|
|
157
|
+
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic evaluator — exact facts / policy / registry lookups.
|
|
3
|
+
* Never invents semantic judgments.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { buildAnswer, concentrationFromScores, clamp01 } from '../confidence.js';
|
|
7
|
+
|
|
8
|
+
export const DETERMINISTIC_EVALUATOR = Object.freeze({
|
|
9
|
+
kind: 'deterministic',
|
|
10
|
+
version: '1',
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {object} question
|
|
14
|
+
*/
|
|
15
|
+
supports(question) {
|
|
16
|
+
if (!question) return false;
|
|
17
|
+
if ([
|
|
18
|
+
'terminal_lane',
|
|
19
|
+
'requires_approval',
|
|
20
|
+
'security_review_required',
|
|
21
|
+
'verification_scope',
|
|
22
|
+
'retrieval_mode',
|
|
23
|
+
'change_risk',
|
|
24
|
+
'skill_candidate',
|
|
25
|
+
].includes(question.id)) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
// Fact-backed generic choose/check — selection still requires state facts at evaluate time.
|
|
29
|
+
if (question.evaluator_hint === 'deterministic' && (question.type === 'choose' || question.type === 'check')) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
return false;
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {object} state
|
|
37
|
+
* @param {object[]} questions
|
|
38
|
+
*/
|
|
39
|
+
async evaluate(state, questions) {
|
|
40
|
+
/** @type {Record<string, object>} */
|
|
41
|
+
const answers = {};
|
|
42
|
+
for (const q of questions) {
|
|
43
|
+
answers[q.id] = evaluateOne(state, q);
|
|
44
|
+
}
|
|
45
|
+
return {
|
|
46
|
+
ok: true,
|
|
47
|
+
evaluator: { kind: 'deterministic', version: '1' },
|
|
48
|
+
answers,
|
|
49
|
+
model_used: false,
|
|
50
|
+
deterministic: true,
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
function evaluateOne(state, question) {
|
|
56
|
+
switch (question.id) {
|
|
57
|
+
case 'terminal_lane':
|
|
58
|
+
return chooseTerminalLane(state, question);
|
|
59
|
+
case 'retrieval_mode':
|
|
60
|
+
return chooseRetrievalMode(state, question);
|
|
61
|
+
case 'verification_scope':
|
|
62
|
+
return chooseVerificationScope(state, question);
|
|
63
|
+
case 'change_risk':
|
|
64
|
+
return scoreChangeRisk(state, question);
|
|
65
|
+
case 'requires_approval':
|
|
66
|
+
return checkRequiresApproval(state, question);
|
|
67
|
+
case 'security_review_required':
|
|
68
|
+
return checkSecurityReview(state, question);
|
|
69
|
+
case 'skill_candidate':
|
|
70
|
+
return chooseSkillCandidate(state, question);
|
|
71
|
+
default:
|
|
72
|
+
if (question.type === 'choose' && state.facts?.[`choose.${question.id}`]) {
|
|
73
|
+
return fromFactChoose(state, question);
|
|
74
|
+
}
|
|
75
|
+
if (question.type === 'check' && state.facts?.[`check.${question.id}`] != null) {
|
|
76
|
+
return fromFactCheck(state, question);
|
|
77
|
+
}
|
|
78
|
+
{
|
|
79
|
+
const err = new Error(`deterministic_unsupported:${question.id}`);
|
|
80
|
+
err.code = 'evaluator_unsupported';
|
|
81
|
+
throw err;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function chooseTerminalLane(state, question) {
|
|
87
|
+
const lanes = state.runtime?.terminal_lanes || {};
|
|
88
|
+
const localOk = lanes.local !== false && lanes.local !== 'unavailable';
|
|
89
|
+
const remoteOk = lanes.remote !== false && lanes.remote !== 'unavailable';
|
|
90
|
+
const sandboxOk = lanes.sandbox !== false && lanes.sandbox !== 'unavailable';
|
|
91
|
+
const isolation = Boolean(state.constraints?.isolation_required || state.facts?.untrusted_code);
|
|
92
|
+
const needsLocal = Boolean(state.constraints?.requires_local_files || state.facts?.requires_local_files);
|
|
93
|
+
|
|
94
|
+
/** @type {Record<string, number>} */
|
|
95
|
+
const scores = { local: 0.1, remote: 0.1, sandbox: 0.1 };
|
|
96
|
+
if (isolation && sandboxOk) scores.sandbox += 5;
|
|
97
|
+
if (needsLocal && localOk) scores.local += 5;
|
|
98
|
+
if (!needsLocal && !isolation && localOk) scores.local += 2;
|
|
99
|
+
if (!needsLocal && remoteOk) scores.remote += 1.5;
|
|
100
|
+
if (!localOk) scores.local = 0;
|
|
101
|
+
if (!remoteOk) scores.remote = 0;
|
|
102
|
+
if (!sandboxOk) scores.sandbox = 0;
|
|
103
|
+
|
|
104
|
+
const value = argmax(scores);
|
|
105
|
+
return buildAnswer({
|
|
106
|
+
type: 'choose',
|
|
107
|
+
question_id: question.id,
|
|
108
|
+
value,
|
|
109
|
+
scores,
|
|
110
|
+
confidence_estimate: concentrationFromScores(scores),
|
|
111
|
+
// No probabilities — heuristic weights, not a probabilistic model
|
|
112
|
+
evaluator: { kind: 'deterministic', version: '1', rule: 'terminal_lane_v1' },
|
|
113
|
+
evidence: [
|
|
114
|
+
`isolation_required=${isolation}`,
|
|
115
|
+
`requires_local_files=${needsLocal}`,
|
|
116
|
+
`local_ok=${localOk}`,
|
|
117
|
+
`remote_ok=${remoteOk}`,
|
|
118
|
+
`sandbox_ok=${sandboxOk}`,
|
|
119
|
+
],
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function chooseRetrievalMode(state, question) {
|
|
124
|
+
const intent = String(state.intent?.type || state.intent || state.facts?.query_kind || '');
|
|
125
|
+
/** @type {Record<string, number>} */
|
|
126
|
+
const scores = {
|
|
127
|
+
ast: 0.2,
|
|
128
|
+
lexical: 0.2,
|
|
129
|
+
semantic: 0.2,
|
|
130
|
+
memory: 0.1,
|
|
131
|
+
web: 0.1,
|
|
132
|
+
none: 0.05,
|
|
133
|
+
};
|
|
134
|
+
if (/symbol|import|definition|call.?graph/i.test(intent)) scores.ast += 4;
|
|
135
|
+
if (/where|file|path|string/i.test(intent)) scores.lexical += 2;
|
|
136
|
+
if (/concept|architecture|why|design/i.test(intent)) scores.semantic += 3;
|
|
137
|
+
if (/last week|history|decision|remember/i.test(intent)) scores.memory += 4;
|
|
138
|
+
if (/cloudflare|provider docs|current support/i.test(intent)) scores.web += 4;
|
|
139
|
+
if (state.facts?.no_retrieval) {
|
|
140
|
+
scores.none += 10;
|
|
141
|
+
}
|
|
142
|
+
const value = argmax(scores);
|
|
143
|
+
return buildAnswer({
|
|
144
|
+
type: 'choose',
|
|
145
|
+
question_id: question.id,
|
|
146
|
+
value,
|
|
147
|
+
scores,
|
|
148
|
+
confidence_estimate: concentrationFromScores(scores),
|
|
149
|
+
evaluator: { kind: 'deterministic', version: '1', rule: 'retrieval_mode_v1' },
|
|
150
|
+
evidence: [`intent=${intent || 'unknown'}`],
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function chooseVerificationScope(state, question) {
|
|
155
|
+
const packages = state.code?.affected_packages?.length || Number(state.facts?.affected_packages) || 0;
|
|
156
|
+
const risk = Number(state.facts?.change_risk_hint ?? 0);
|
|
157
|
+
/** @type {Record<string, number>} */
|
|
158
|
+
const scores = { focused: 1, package: 1, workspace: 0.5, repository: 0.2 };
|
|
159
|
+
if (packages <= 1 && risk < 2) scores.focused += 3;
|
|
160
|
+
else if (packages <= 3 && risk < 3) scores.package += 3;
|
|
161
|
+
else if (packages <= 8) scores.workspace += 3;
|
|
162
|
+
else scores.repository += 3;
|
|
163
|
+
const value = argmax(scores);
|
|
164
|
+
return buildAnswer({
|
|
165
|
+
type: 'choose',
|
|
166
|
+
question_id: question.id,
|
|
167
|
+
value,
|
|
168
|
+
scores,
|
|
169
|
+
confidence_estimate: concentrationFromScores(scores),
|
|
170
|
+
evaluator: { kind: 'deterministic', version: '1', rule: 'verification_scope_v1' },
|
|
171
|
+
evidence: [`affected_packages=${packages}`, `risk_hint=${risk}`],
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function scoreChangeRisk(state, question) {
|
|
176
|
+
const levels = question.levels;
|
|
177
|
+
let index = 0;
|
|
178
|
+
const packages = state.code?.affected_packages?.length || 0;
|
|
179
|
+
if (state.facts?.security_boundary || state.facts?.schema_change) index = Math.max(index, 3);
|
|
180
|
+
if (state.facts?.production_sensitive) index = Math.max(index, 4);
|
|
181
|
+
if (packages > 5) index = Math.max(index, 2);
|
|
182
|
+
else if (packages > 1) index = Math.max(index, 1);
|
|
183
|
+
if (state.facts?.runtime_change) index = Math.max(index, 3);
|
|
184
|
+
index = Math.min(index, levels[levels.length - 1].index);
|
|
185
|
+
|
|
186
|
+
/** @type {Record<string, number>} */
|
|
187
|
+
const scores = {};
|
|
188
|
+
for (const level of levels) {
|
|
189
|
+
const dist = Math.abs(level.index - index);
|
|
190
|
+
scores[String(level.index)] = Math.max(0.05, 1 / (1 + dist * dist));
|
|
191
|
+
}
|
|
192
|
+
return buildAnswer({
|
|
193
|
+
type: 'score',
|
|
194
|
+
question_id: question.id,
|
|
195
|
+
value: index,
|
|
196
|
+
levels: Object.fromEntries(levels.map((l) => [String(l.index), l.label])),
|
|
197
|
+
scores,
|
|
198
|
+
confidence_estimate: concentrationFromScores(scores),
|
|
199
|
+
evaluator: { kind: 'deterministic', version: '1', rule: 'change_risk_v1' },
|
|
200
|
+
evidence: [
|
|
201
|
+
`packages=${packages}`,
|
|
202
|
+
`security_boundary=${Boolean(state.facts?.security_boundary)}`,
|
|
203
|
+
`schema_change=${Boolean(state.facts?.schema_change)}`,
|
|
204
|
+
`production_sensitive=${Boolean(state.facts?.production_sensitive)}`,
|
|
205
|
+
],
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function checkRequiresApproval(state, question) {
|
|
210
|
+
const production = Boolean(
|
|
211
|
+
state.constraints?.production_write
|
|
212
|
+
|| state.facts?.production_write
|
|
213
|
+
|| state.constraints?.approval === 'always',
|
|
214
|
+
);
|
|
215
|
+
const irreversible = Boolean(state.facts?.irreversible);
|
|
216
|
+
const highRisk = Number(state.facts?.change_risk_hint ?? 0) >= 3;
|
|
217
|
+
const value = production || irreversible || highRisk;
|
|
218
|
+
const support = value ? 0.95 : 0.08;
|
|
219
|
+
return buildAnswer({
|
|
220
|
+
type: 'check',
|
|
221
|
+
question_id: question.id,
|
|
222
|
+
value,
|
|
223
|
+
support: clamp01(support),
|
|
224
|
+
// Explicitly NOT calibrated_probability
|
|
225
|
+
evaluator: { kind: 'deterministic', version: '1', rule: 'requires_approval_v1' },
|
|
226
|
+
evidence: [
|
|
227
|
+
`production_write=${production}`,
|
|
228
|
+
`irreversible=${irreversible}`,
|
|
229
|
+
`high_risk=${highRisk}`,
|
|
230
|
+
],
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function checkSecurityReview(state, question) {
|
|
235
|
+
const value = Boolean(
|
|
236
|
+
state.facts?.security_boundary
|
|
237
|
+
|| state.facts?.auth_change
|
|
238
|
+
|| state.facts?.secret_handling
|
|
239
|
+
|| state.constraints?.security === 'review',
|
|
240
|
+
);
|
|
241
|
+
return buildAnswer({
|
|
242
|
+
type: 'check',
|
|
243
|
+
question_id: question.id,
|
|
244
|
+
value,
|
|
245
|
+
support: value ? 0.9 : 0.12,
|
|
246
|
+
evaluator: { kind: 'deterministic', version: '1', rule: 'security_review_v1' },
|
|
247
|
+
evidence: [
|
|
248
|
+
`security_boundary=${Boolean(state.facts?.security_boundary)}`,
|
|
249
|
+
`auth_change=${Boolean(state.facts?.auth_change)}`,
|
|
250
|
+
],
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function chooseSkillCandidate(state, question) {
|
|
255
|
+
const options = Array.isArray(question.options) ? question.options : [];
|
|
256
|
+
if (!options.length) {
|
|
257
|
+
const err = new Error('skill_candidate_requires_options');
|
|
258
|
+
err.code = 'invalid_question';
|
|
259
|
+
throw err;
|
|
260
|
+
}
|
|
261
|
+
const intent = String(state.intent?.text || state.intent?.type || state.intent || '').toLowerCase();
|
|
262
|
+
const tags = Array.isArray(state.facts?.intent_tags)
|
|
263
|
+
? state.facts.intent_tags.map((t) => String(t).toLowerCase())
|
|
264
|
+
: intent.split(/\W+/).filter(Boolean);
|
|
265
|
+
const criteria = question.criteria && typeof question.criteria === 'object' ? question.criteria : {};
|
|
266
|
+
const skills = state.skills && typeof state.skills === 'object' ? state.skills : {};
|
|
267
|
+
|
|
268
|
+
/** @type {Record<string, number>} */
|
|
269
|
+
const scores = {};
|
|
270
|
+
for (const opt of options) {
|
|
271
|
+
let score = 0.1;
|
|
272
|
+
const def = criteria[opt] || skills[opt] || {};
|
|
273
|
+
const hay = [
|
|
274
|
+
opt,
|
|
275
|
+
def.label,
|
|
276
|
+
def.description,
|
|
277
|
+
...(Array.isArray(def.tags) ? def.tags : []),
|
|
278
|
+
...(Array.isArray(def.use_when) ? def.use_when : []),
|
|
279
|
+
]
|
|
280
|
+
.filter(Boolean)
|
|
281
|
+
.join(' ')
|
|
282
|
+
.toLowerCase();
|
|
283
|
+
for (const tag of tags) {
|
|
284
|
+
if (tag.length >= 3 && hay.includes(tag)) score += 1.5;
|
|
285
|
+
}
|
|
286
|
+
if (intent && hay && intent.split(/\s+/).some((w) => w.length >= 4 && hay.includes(w))) {
|
|
287
|
+
score += 2;
|
|
288
|
+
}
|
|
289
|
+
scores[opt] = score;
|
|
290
|
+
}
|
|
291
|
+
const value = argmax(scores);
|
|
292
|
+
return buildAnswer({
|
|
293
|
+
type: 'choose',
|
|
294
|
+
question_id: question.id,
|
|
295
|
+
value,
|
|
296
|
+
scores,
|
|
297
|
+
confidence_estimate: concentrationFromScores(scores),
|
|
298
|
+
evaluator: { kind: 'deterministic', version: '1', rule: 'skill_candidate_v1' },
|
|
299
|
+
evidence: [`intent=${intent || 'unknown'}`, `candidates=${options.join(',')}`],
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function fromFactChoose(state, question) {
|
|
304
|
+
const value = state.facts[`choose.${question.id}`];
|
|
305
|
+
/** @type {Record<string, number>} */
|
|
306
|
+
const scores = {};
|
|
307
|
+
for (const opt of question.options) scores[opt] = opt === value ? 1 : 0;
|
|
308
|
+
return buildAnswer({
|
|
309
|
+
type: 'choose',
|
|
310
|
+
question_id: question.id,
|
|
311
|
+
value,
|
|
312
|
+
scores,
|
|
313
|
+
confidence_estimate: 1,
|
|
314
|
+
evaluator: { kind: 'deterministic', version: '1', rule: 'fact_choose' },
|
|
315
|
+
evidence: [`fact=choose.${question.id}`],
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function fromFactCheck(state, question) {
|
|
320
|
+
const value = Boolean(state.facts[`check.${question.id}`]);
|
|
321
|
+
return buildAnswer({
|
|
322
|
+
type: 'check',
|
|
323
|
+
question_id: question.id,
|
|
324
|
+
value,
|
|
325
|
+
support: value ? 1 : 0,
|
|
326
|
+
evaluator: { kind: 'deterministic', version: '1', rule: 'fact_check' },
|
|
327
|
+
evidence: [`fact=check.${question.id}`],
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function argmax(scores) {
|
|
332
|
+
let best = null;
|
|
333
|
+
let bestV = -Infinity;
|
|
334
|
+
for (const [k, v] of Object.entries(scores)) {
|
|
335
|
+
if (v > bestV || (v === bestV && (best == null || k < best))) {
|
|
336
|
+
best = k;
|
|
337
|
+
bestV = v;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return best;
|
|
341
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Heuristic evaluator — explicit utility formulas over state facts.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { buildAnswer, concentrationFromScores, clamp01 } from '../confidence.js';
|
|
6
|
+
|
|
7
|
+
export const HEURISTIC_EVALUATOR = Object.freeze({
|
|
8
|
+
kind: 'heuristic',
|
|
9
|
+
version: '1',
|
|
10
|
+
|
|
11
|
+
supports(question) {
|
|
12
|
+
return question?.evaluator_hint === 'heuristic';
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
async evaluate(state, questions) {
|
|
16
|
+
/** @type {Record<string, object>} */
|
|
17
|
+
const answers = {};
|
|
18
|
+
for (const q of questions) {
|
|
19
|
+
if (q.type === 'score' && typeof state.facts?.[`score.${q.id}`] === 'number') {
|
|
20
|
+
const value = Number(state.facts[`score.${q.id}`]);
|
|
21
|
+
/** @type {Record<string, number>} */
|
|
22
|
+
const scores = {};
|
|
23
|
+
for (const level of q.levels) {
|
|
24
|
+
const dist = Math.abs(level.index - value);
|
|
25
|
+
scores[String(level.index)] = Math.max(0.01, 1 / (1 + dist));
|
|
26
|
+
}
|
|
27
|
+
answers[q.id] = buildAnswer({
|
|
28
|
+
type: 'score',
|
|
29
|
+
question_id: q.id,
|
|
30
|
+
value,
|
|
31
|
+
levels: Object.fromEntries(q.levels.map((l) => [String(l.index), l.label])),
|
|
32
|
+
scores,
|
|
33
|
+
confidence_estimate: concentrationFromScores(scores),
|
|
34
|
+
evaluator: { kind: 'heuristic', version: '1' },
|
|
35
|
+
});
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
if (q.type === 'check' && typeof state.facts?.[`support.${q.id}`] === 'number') {
|
|
39
|
+
const support = clamp01(Number(state.facts[`support.${q.id}`]));
|
|
40
|
+
answers[q.id] = buildAnswer({
|
|
41
|
+
type: 'check',
|
|
42
|
+
question_id: q.id,
|
|
43
|
+
value: support >= 0.5,
|
|
44
|
+
support,
|
|
45
|
+
evaluator: { kind: 'heuristic', version: '1' },
|
|
46
|
+
});
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
const err = new Error(`heuristic_unsupported:${q.id}`);
|
|
50
|
+
err.code = 'evaluator_unsupported';
|
|
51
|
+
throw err;
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
ok: true,
|
|
55
|
+
evaluator: { kind: 'heuristic', version: '1' },
|
|
56
|
+
answers,
|
|
57
|
+
model_used: false,
|
|
58
|
+
deterministic: true,
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Evaluator selection — cheapest reliable evaluator first.
|
|
3
|
+
* Deterministic facts MUST NOT silently fall through to semantic.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { DETERMINISTIC_EVALUATOR } from './deterministic.js';
|
|
7
|
+
import { HEURISTIC_EVALUATOR } from './heuristic.js';
|
|
8
|
+
import { SEMANTIC_EVALUATOR } from './semantic.js';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_CHAIN = [DETERMINISTIC_EVALUATOR, HEURISTIC_EVALUATOR, SEMANTIC_EVALUATOR];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @param {object} question
|
|
14
|
+
* @param {object[]} [evaluators]
|
|
15
|
+
*/
|
|
16
|
+
export function selectEvaluator(question, evaluators = DEFAULT_CHAIN) {
|
|
17
|
+
if (question.evaluator_hint === 'semantic') {
|
|
18
|
+
const semantic = evaluators.find((e) => e.kind === 'semantic');
|
|
19
|
+
if (!semantic?.supports(question)) {
|
|
20
|
+
const err = new Error('semantic_evaluator_required_but_unavailable');
|
|
21
|
+
err.code = 'evaluator_unavailable';
|
|
22
|
+
throw err;
|
|
23
|
+
}
|
|
24
|
+
return semantic;
|
|
25
|
+
}
|
|
26
|
+
if (question.evaluator_hint === 'heuristic') {
|
|
27
|
+
const heuristic = evaluators.find((e) => e.kind === 'heuristic');
|
|
28
|
+
if (!heuristic?.supports(question)) {
|
|
29
|
+
const err = new Error('heuristic_evaluator_required_but_unavailable');
|
|
30
|
+
err.code = 'evaluator_unavailable';
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
return heuristic;
|
|
34
|
+
}
|
|
35
|
+
if (question.evaluator_hint === 'deterministic' || !question.evaluator_hint) {
|
|
36
|
+
for (const ev of evaluators) {
|
|
37
|
+
if (ev.kind === 'semantic') continue; // never silent semantic fallback
|
|
38
|
+
if (ev.supports(question)) return ev;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
for (const ev of evaluators) {
|
|
42
|
+
if (ev.kind === 'semantic') continue;
|
|
43
|
+
if (ev.supports(question)) return ev;
|
|
44
|
+
}
|
|
45
|
+
const err = new Error(`no_evaluator_for:${question.id}`);
|
|
46
|
+
err.code = 'evaluator_unsupported';
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export { DETERMINISTIC_EVALUATOR, HEURISTIC_EVALUATOR, SEMANTIC_EVALUATOR, DEFAULT_CHAIN };
|