@chatpanel/events 0.101.0 → 0.103.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/adapters.js +1 -83
- package/adaptive-tool-policy.js +1 -45
- package/agent.js +2 -255
- package/attribution.js +1 -132
- package/backup-envelope.js +1 -221
- package/board-tool.js +2 -165
- package/budget.js +1 -116
- package/capability.js +1 -126
- package/citations.js +5 -127
- package/client-prefs.js +1 -148
- package/context-attachments.js +8 -144
- package/cowriter-router.js +1 -83
- package/cowriter-writer.js +19 -193
- package/cowriter.js +1 -190
- package/curate.js +3 -509
- package/distance.js +1 -124
- package/engine.js +1 -131
- package/entitlement.js +1 -332
- package/entity.js +1 -354
- package/event.js +1 -175
- package/extraction.js +4 -293
- package/failover.js +1 -93
- package/find-tool.js +1 -66
- package/flowchart.js +2 -644
- package/gate.js +1 -74
- package/harness.js +4 -101
- package/index.js +1 -345
- package/invariants.js +1 -174
- package/job.js +3 -147
- package/kernel.js +1 -255
- package/knowledge-derive.js +1 -267
- package/knowledge.js +3 -230
- package/library.js +11 -280
- package/loop.js +1 -132
- package/manifest.js +1 -107
- package/markdown-authoring.js +11 -288
- package/markdown-render.js +0 -0
- package/mcp-client.js +8 -358
- package/mcp-dispatch.js +1 -52
- package/mcp-errors.js +1 -106
- package/mcp-manager.js +1 -127
- package/media-transcript.js +8 -531
- package/meeting-analyzers.js +1 -83
- package/meeting-insights.js +7 -164
- package/meeting-shape.js +1 -282
- package/meeting-text.js +5 -94
- package/memory.js +2 -638
- package/model-candidates.js +1 -358
- package/model-health.js +1 -182
- package/model-ledger.js +1 -228
- package/model-picker.js +1 -188
- package/note-actions.js +15 -246
- package/note-graph.js +1 -158
- package/note-links.js +1 -68
- package/note-mentions.js +8 -114
- package/note-plan.js +9 -107
- package/note-research.js +1 -135
- package/observability.js +1 -119
- package/omni.js +1 -125
- package/order.js +1 -78
- package/package.json +1 -4
- package/pdf-layout.js +7 -253
- package/project-run.js +20 -444
- package/project.js +1 -170
- package/promotion.js +1 -171
- package/queue.js +1 -98
- package/reach.js +1 -23
- package/recipe-tool.js +2 -161
- package/recipe.js +1 -264
- package/record-list.js +2 -110
- package/recruit.js +3 -456
- package/redaction-tokens.js +1 -61
- package/ref.js +1 -55
- package/registry.js +1 -240
- package/route-graph.js +1 -115
- package/route-strategies.js +2 -232
- package/router.js +2 -880
- package/rrf.js +1 -89
- package/rules.js +1 -142
- package/schedule.js +1 -663
- package/scm-connection.js +1 -180
- package/scopes.js +1 -12
- package/scorecard.js +1 -335
- package/search-engines.js +1 -81
- package/skill-manifest.js +1 -330
- package/skill-scan.js +3 -209
- package/skill-sources.js +1 -162
- package/skill-vars.js +2 -252
- package/slash-commands.js +1 -158
- package/source-gate.js +1 -92
- package/sources-retrieval.js +5 -186
- package/sources.js +1 -273
- package/store.js +1 -171
- package/structured.js +4 -901
- package/subject-kinds.js +1 -5
- package/subject-name.js +1 -96
- package/sync-plan.js +1 -213
- package/synthesis.js +3 -123
- package/tags.js +1 -203
- package/team-board.js +6 -337
- package/team-cache.js +2 -63
- package/team-observe.js +1 -118
- package/team-org.js +1 -348
- package/team-plan.js +4 -131
- package/team-record.js +1 -225
- package/team-run.js +9 -652
- package/team-subtask.js +2 -191
- package/team-task.js +2 -124
- package/team-tool.js +2 -196
- package/team-trail.js +2 -73
- package/team-worklog.js +2 -149
- package/team.js +1 -302
- package/text-search.js +1 -155
- package/theme.js +17 -154
- package/titles.js +4 -285
- package/tool-discovery.js +1 -87
- package/tool-dispatch.js +5 -230
- package/tool-groups.js +1 -81
- package/tool-hints.js +10 -185
- package/tool-loop-guard.js +4 -182
- package/tool-need.js +2 -124
- package/tool-result.js +2 -438
- package/tool-round.js +0 -0
- package/tool-schema.js +1 -155
- package/tool-traits.js +1 -182
- package/toolset.js +2 -88
- package/trajectory.js +1 -530
- package/turn-loop.js +4 -440
- package/upcast.js +1 -37
- package/vault.js +2 -250
- package/view.js +1 -88
- package/voice-intents.js +2 -1488
- package/voice-speaker.js +1 -98
- package/weather-tool.js +1 -65
- package/weather.js +2 -211
- package/web-search-tool.js +14 -102
- package/web-search.js +1 -160
- package/widget.js +1 -153
package/scm-connection.js
CHANGED
|
@@ -1,180 +1 @@
|
|
|
1
|
-
|
|
2
|
-
// and the two names a job gets in it: its branch and its worktree directory.
|
|
3
|
-
// (architecture-pillars.md §14: the repository is the source of truth.)
|
|
4
|
-
//
|
|
5
|
-
// ChatPanel is not a git host and not a git client. The harness runs `git`, `gh`, `glab`;
|
|
6
|
-
// ChatPanel brings the CREDENTIAL, the WORKTREE, the BRANCH NAME and the RECORD. This module
|
|
7
|
-
// is the pure part: what a connection record holds, how a remote URL maps to one, what a
|
|
8
|
-
// job's branch is called, and the environment that hands a token to git for ONE process
|
|
9
|
-
// without writing it anywhere.
|
|
10
|
-
//
|
|
11
|
-
// THE TOKEN IS NEVER IN THE RECORD. A connection stores a `secretRef` — the name under which
|
|
12
|
-
// the machine's keychain (or the bridge's secret store) holds the token. The record travels
|
|
13
|
-
// with the client-prefs document (`connections` section) so both clients show the same
|
|
14
|
-
// connections; the secret does not travel, and `normalizeConnection` REFUSES a record that
|
|
15
|
-
// carries one, so a settings export, a sync, or a prompt can never carry it either.
|
|
16
|
-
|
|
17
|
-
export const SCM_KINDS = Object.freeze(['github', 'github-enterprise', 'gitlab', 'bitbucket', 'gitea', 'azure-devops', 'git']);
|
|
18
|
-
export const CONNECTION_ID_RE = /^[a-z][a-z0-9_-]{0,63}$/i;
|
|
19
|
-
export const SECRET_REF_RE = /^[a-zA-Z0-9_.:@/-]{1,200}$/;
|
|
20
|
-
const SECRET_FIELDS = ['token', 'password', 'secret', 'pat', 'apiKey', 'key'];
|
|
21
|
-
const DEFAULT_HOST = { github: 'github.com', gitlab: 'gitlab.com', bitbucket: 'bitbucket.org', 'azure-devops': 'dev.azure.com' };
|
|
22
|
-
|
|
23
|
-
export class ScmError extends Error {
|
|
24
|
-
constructor(code, message) { super(message); this.name = 'ScmError'; this.code = code; }
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const isRecord = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
28
|
-
|
|
29
|
-
/** The host a connection serves: its baseUrl's, or the kind's public host. */
|
|
30
|
-
export function hostOf(conn) {
|
|
31
|
-
const url = String(conn?.baseUrl || '').trim();
|
|
32
|
-
if (url) { try { return new URL(/^https?:\/\//i.test(url) ? url : `https://${url}`).host.toLowerCase(); } catch { return url.toLowerCase().replace(/^https?:\/\//, '').split('/')[0]; } }
|
|
33
|
-
return DEFAULT_HOST[conn?.kind] || '';
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function validateConnection(conn) {
|
|
37
|
-
const errors = [];
|
|
38
|
-
if (!isRecord(conn)) return { ok: false, errors: ['connection must be an object'] };
|
|
39
|
-
if (!CONNECTION_ID_RE.test(String(conn.id || ''))) errors.push('id: a short identifier (letters, digits, _ -)');
|
|
40
|
-
if (!SCM_KINDS.includes(conn.kind)) errors.push(`kind: one of ${SCM_KINDS.join(', ')}`);
|
|
41
|
-
const needsHost = ['github-enterprise', 'gitea', 'git'].includes(conn.kind);
|
|
42
|
-
if (needsHost && !String(conn.baseUrl || '').trim()) errors.push('baseUrl: the hub\'s URL');
|
|
43
|
-
if (conn.baseUrl && !/^(https?:\/\/)?[a-z0-9.-]+(:\d+)?(\/[^\s]*)?$/i.test(String(conn.baseUrl).trim())) errors.push('baseUrl: a host or URL');
|
|
44
|
-
const leaked = SECRET_FIELDS.filter((k) => conn[k] !== undefined && conn[k] !== null && conn[k] !== '');
|
|
45
|
-
if (leaked.length) errors.push(`${leaked.join(', ')}: a connection never stores a secret — store it in the keychain and reference it by secretRef`);
|
|
46
|
-
if (conn.secretRef !== undefined && conn.secretRef !== null && conn.secretRef !== '' && !SECRET_REF_RE.test(String(conn.secretRef))) errors.push('secretRef: a keychain / secret-store name');
|
|
47
|
-
if (conn.reach !== undefined && !Array.isArray(conn.reach)) errors.push('reach: a list of remotes or owners this connection may be used for');
|
|
48
|
-
return { ok: errors.length === 0, errors };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** The stored form. Throws on a record that carries a secret. */
|
|
52
|
-
export function normalizeConnection(conn) {
|
|
53
|
-
const v = validateConnection(conn);
|
|
54
|
-
if (!v.ok) throw new ScmError('INVALID', v.errors.join('; '));
|
|
55
|
-
const id = String(conn.id);
|
|
56
|
-
return {
|
|
57
|
-
id,
|
|
58
|
-
kind: conn.kind,
|
|
59
|
-
label: String(conn.label || conn.name || '').trim().slice(0, 80) || `${conn.kind} · ${hostOf(conn) || id}`,
|
|
60
|
-
...(conn.baseUrl ? { baseUrl: String(conn.baseUrl).trim().replace(/\/+$/, '') } : {}),
|
|
61
|
-
// The secret's NAME — `chatpanel:scm:<id>` by default — under which the keychain holds it.
|
|
62
|
-
secretRef: String(conn.secretRef || '').trim() || `chatpanel:scm:${id}`,
|
|
63
|
-
...(conn.username ? { username: String(conn.username).trim().slice(0, 80) } : {}),
|
|
64
|
-
// Which remotes it may be used for: `owner/*`, `owner/name`, or a host. Empty = any on its host.
|
|
65
|
-
reach: (Array.isArray(conn.reach) ? conn.reach : []).map((r) => String(r).trim().toLowerCase()).filter(Boolean).slice(0, 64),
|
|
66
|
-
enabled: conn.enabled !== false,
|
|
67
|
-
...(conn.createdAt ? { createdAt: conn.createdAt } : {}),
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* A remote URL, read: `{ host, owner, name, protocol }` for https, ssh (`git@host:o/n.git`,
|
|
73
|
-
* `ssh://git@host/o/n`) and plain `host/o/n`. Credentials in the URL are dropped, never
|
|
74
|
-
* returned. Null for what cannot be read.
|
|
75
|
-
*/
|
|
76
|
-
export function parseRemote(url) {
|
|
77
|
-
const s = String(url || '').trim();
|
|
78
|
-
if (!s) return null;
|
|
79
|
-
let m = /^(?:ssh:\/\/)?(?:[\w.-]+@)?([a-z0-9.-]+)(?::\d+)?[:/]+([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i.exec(s.replace(/^https?:\/\/(?:[^@/]+@)?/i, ''));
|
|
80
|
-
if (m && !/^https?:/i.test(s)) return { host: m[1].toLowerCase(), owner: m[2], name: m[3], protocol: /^ssh:|^[\w.-]+@/.test(s) ? 'ssh' : 'plain' };
|
|
81
|
-
try {
|
|
82
|
-
const u = new URL(s);
|
|
83
|
-
const parts = u.pathname.replace(/\.git$/, '').split('/').filter(Boolean);
|
|
84
|
-
if (parts.length < 2) return null;
|
|
85
|
-
// Azure DevOps: /org/project/_git/repo; GitLab subgroups: /a/b/c/repo — owner is all but the last.
|
|
86
|
-
const name = parts.at(-1);
|
|
87
|
-
const owner = parts.slice(0, -1).filter((p) => p !== '_git').join('/');
|
|
88
|
-
return { host: u.host.toLowerCase(), owner, name, protocol: u.protocol.replace(':', '') };
|
|
89
|
-
} catch { return null; }
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* The connection to use for a remote: enabled, same host, and — when the connection lists
|
|
94
|
-
* a reach — the remote's `owner/name` or `owner/*` in it. The most specific reach wins.
|
|
95
|
-
*/
|
|
96
|
-
export function connectionFor(remote, connections = []) {
|
|
97
|
-
const r = typeof remote === 'string' ? parseRemote(remote) : remote;
|
|
98
|
-
if (!r) return null;
|
|
99
|
-
const slug = `${r.owner}/${r.name}`.toLowerCase();
|
|
100
|
-
let best = null; let bestScore = -1;
|
|
101
|
-
for (const c of connections || []) {
|
|
102
|
-
if (!c || c.enabled === false) continue;
|
|
103
|
-
if (hostOf(c) !== r.host) continue;
|
|
104
|
-
const reach = Array.isArray(c.reach) ? c.reach : [];
|
|
105
|
-
let score = reach.length ? -1 : 0;
|
|
106
|
-
for (const x of reach) {
|
|
107
|
-
if (x === slug) score = Math.max(score, 3);
|
|
108
|
-
else if (x.endsWith('/*') && slug.startsWith(x.slice(0, -1))) score = Math.max(score, 2);
|
|
109
|
-
else if (x === r.host) score = Math.max(score, 1);
|
|
110
|
-
}
|
|
111
|
-
if (score > bestScore) { best = c; bestScore = score; }
|
|
112
|
-
}
|
|
113
|
-
return best;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const slug = (s) => String(s || '').trim().toLowerCase().replace(/[^a-z0-9._-]+/g, '-').replace(/^[-.]+|[-.]+$/g, '').slice(0, 48) || 'x';
|
|
117
|
-
|
|
118
|
-
/** A job's branch: `cp/<project>/<job>` — one branch per job, always under `cp/`, never a protected name. */
|
|
119
|
-
export function branchFor(projectId, jobId) { return `cp/${slug(projectId)}/${slug(jobId)}`; }
|
|
120
|
-
|
|
121
|
-
/** A job's worktree directory under the bridge's worktree root: `<project>/<job>`. */
|
|
122
|
-
export function worktreeDirFor(projectId, jobId) { return `${slug(projectId)}/${slug(jobId)}`; }
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* The environment that hands ONE process the token, scoped to the connection's host:
|
|
126
|
-
* git's `credential.<url>.helper` set through `GIT_CONFIG_*` (never a file, never the
|
|
127
|
-
* command line), a helper that reads the token from the environment, terminal prompts
|
|
128
|
-
* off, and the hub CLIs' own variables (`GH_TOKEN` / `GH_HOST`, `GITLAB_TOKEN`) so `gh` and
|
|
129
|
-
* `glab` work in the same process. The caller spawns with `{ ...process.env, ...env }` and
|
|
130
|
-
* the token dies with the process.
|
|
131
|
-
*/
|
|
132
|
-
export function credentialEnv(conn, token, { username = null } = {}) {
|
|
133
|
-
const c = conn || {};
|
|
134
|
-
const host = hostOf(c);
|
|
135
|
-
if (!host || !token) return {};
|
|
136
|
-
const user = username || c.username || ({ github: 'x-access-token', 'github-enterprise': 'x-access-token', gitlab: 'oauth2', bitbucket: 'x-token-auth', gitea: 'oauth2', 'azure-devops': 'pat' }[c.kind] || 'token');
|
|
137
|
-
const env = {
|
|
138
|
-
GIT_TERMINAL_PROMPT: '0',
|
|
139
|
-
GIT_CONFIG_COUNT: '2',
|
|
140
|
-
GIT_CONFIG_KEY_0: `credential.https://${host}.helper`,
|
|
141
|
-
// A shell helper: on `get`, answer from the environment. Nothing is written anywhere.
|
|
142
|
-
GIT_CONFIG_VALUE_0: '!f() { if [ "$1" = get ]; then printf "username=%s\\npassword=%s\\n" "$CHATPANEL_SCM_USER" "$CHATPANEL_SCM_TOKEN"; fi; }; f',
|
|
143
|
-
GIT_CONFIG_KEY_1: `credential.https://${host}.useHttpPath`,
|
|
144
|
-
GIT_CONFIG_VALUE_1: 'false',
|
|
145
|
-
CHATPANEL_SCM_USER: user,
|
|
146
|
-
CHATPANEL_SCM_TOKEN: String(token),
|
|
147
|
-
};
|
|
148
|
-
if (c.kind === 'github' || c.kind === 'github-enterprise') {
|
|
149
|
-
env.GH_TOKEN = String(token);
|
|
150
|
-
if (c.kind === 'github-enterprise') { env.GH_ENTERPRISE_TOKEN = String(token); env.GH_HOST = host; }
|
|
151
|
-
}
|
|
152
|
-
if (c.kind === 'gitlab') { env.GITLAB_TOKEN = String(token); env.GITLAB_HOST = host; }
|
|
153
|
-
return env;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** One line a person reads: "GitHub · github.com · acme/*". */
|
|
157
|
-
export function describeConnection(conn) {
|
|
158
|
-
const c = conn || {};
|
|
159
|
-
const kindLabel = { github: 'GitHub', 'github-enterprise': 'GitHub Enterprise', gitlab: 'GitLab', bitbucket: 'Bitbucket', gitea: 'Gitea', 'azure-devops': 'Azure DevOps', git: 'Git' }[c.kind] || c.kind;
|
|
160
|
-
return `${kindLabel} · ${hostOf(c)}${c.reach?.length ? ` · ${c.reach.join(', ')}` : ''}${c.enabled === false ? ' (off)' : ''}`;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export function blankConnection() { return { id: '', kind: 'github', label: '', baseUrl: '', reach: [], username: '' }; }
|
|
164
|
-
|
|
165
|
-
/** The editor's form → a connection, or the errors. The token is NOT part of the form's record: the host stores it separately. */
|
|
166
|
-
export function connectionFromForm(form) {
|
|
167
|
-
const f = form || {};
|
|
168
|
-
const id = String(f.id || '').trim() || slug(f.label || `${f.kind}-${hostOf(f)}`);
|
|
169
|
-
const conn = {
|
|
170
|
-
id, kind: f.kind || 'github', label: String(f.label || '').trim(),
|
|
171
|
-
...(f.baseUrl ? { baseUrl: String(f.baseUrl).trim() } : {}),
|
|
172
|
-
...(f.username ? { username: String(f.username).trim() } : {}),
|
|
173
|
-
...(f.secretRef ? { secretRef: String(f.secretRef).trim() } : {}),
|
|
174
|
-
reach: String(Array.isArray(f.reach) ? f.reach.join(',') : f.reach || '').split(/[,\s]+/).map((x) => x.trim()).filter(Boolean),
|
|
175
|
-
enabled: f.enabled !== false,
|
|
176
|
-
...(f.createdAt ? { createdAt: f.createdAt } : {}),
|
|
177
|
-
};
|
|
178
|
-
const v = validateConnection(conn);
|
|
179
|
-
return v.ok ? { ok: true, connection: normalizeConnection(conn) } : { ok: false, errors: v.errors };
|
|
180
|
-
}
|
|
1
|
+
const f=Object.freeze(["github","github-enterprise","gitlab","bitbucket","gitea","azure-devops","git"]),b=/^[a-z][a-z0-9_-]{0,63}$/i,d=/^[a-zA-Z0-9_.:@/-]{1,200}$/,g=["token","password","secret","pat","apiKey","key"],m={github:"github.com",gitlab:"gitlab.com",bitbucket:"bitbucket.org","azure-devops":"dev.azure.com"};class _ extends Error{constructor(e,i){super(i),this.name="ScmError",this.code=e}}const S=t=>t!==null&&typeof t=="object"&&!Array.isArray(t);function c(t){const e=String(t?.baseUrl||"").trim();if(e)try{return new URL(/^https?:\/\//i.test(e)?e:`https://${e}`).host.toLowerCase()}catch{return e.toLowerCase().replace(/^https?:\/\//,"").split("/")[0]}return m[t?.kind]||""}function p(t){const e=[];if(!S(t))return{ok:!1,errors:["connection must be an object"]};b.test(String(t.id||""))||e.push("id: a short identifier (letters, digits, _ -)"),f.includes(t.kind)||e.push(`kind: one of ${f.join(", ")}`),["github-enterprise","gitea","git"].includes(t.kind)&&!String(t.baseUrl||"").trim()&&e.push("baseUrl: the hub's URL"),t.baseUrl&&!/^(https?:\/\/)?[a-z0-9.-]+(:\d+)?(\/[^\s]*)?$/i.test(String(t.baseUrl).trim())&&e.push("baseUrl: a host or URL");const r=g.filter(s=>t[s]!==void 0&&t[s]!==null&&t[s]!=="");return r.length&&e.push(`${r.join(", ")}: a connection never stores a secret — store it in the keychain and reference it by secretRef`),t.secretRef!==void 0&&t.secretRef!==null&&t.secretRef!==""&&!d.test(String(t.secretRef))&&e.push("secretRef: a keychain / secret-store name"),t.reach!==void 0&&!Array.isArray(t.reach)&&e.push("reach: a list of remotes or owners this connection may be used for"),{ok:e.length===0,errors:e}}function k(t){const e=p(t);if(!e.ok)throw new _("INVALID",e.errors.join("; "));const i=String(t.id);return{id:i,kind:t.kind,label:String(t.label||t.name||"").trim().slice(0,80)||`${t.kind} · ${c(t)||i}`,...t.baseUrl?{baseUrl:String(t.baseUrl).trim().replace(/\/+$/,"")}:{},secretRef:String(t.secretRef||"").trim()||`chatpanel:scm:${i}`,...t.username?{username:String(t.username).trim().slice(0,80)}:{},reach:(Array.isArray(t.reach)?t.reach:[]).map(r=>String(r).trim().toLowerCase()).filter(Boolean).slice(0,64),enabled:t.enabled!==!1,...t.createdAt?{createdAt:t.createdAt}:{}}}function E(t){const e=String(t||"").trim();if(!e)return null;let i=/^(?:ssh:\/\/)?(?:[\w.-]+@)?([a-z0-9.-]+)(?::\d+)?[:/]+([^/\s]+)\/([^/\s]+?)(?:\.git)?\/?$/i.exec(e.replace(/^https?:\/\/(?:[^@/]+@)?/i,""));if(i&&!/^https?:/i.test(e))return{host:i[1].toLowerCase(),owner:i[2],name:i[3],protocol:/^ssh:|^[\w.-]+@/.test(e)?"ssh":"plain"};try{const r=new URL(e),s=r.pathname.replace(/\.git$/,"").split("/").filter(Boolean);if(s.length<2)return null;const a=s.at(-1),n=s.slice(0,-1).filter(u=>u!=="_git").join("/");return{host:r.host.toLowerCase(),owner:n,name:a,protocol:r.protocol.replace(":","")}}catch{return null}}function A(t,e=[]){const i=typeof t=="string"?E(t):t;if(!i)return null;const r=`${i.owner}/${i.name}`.toLowerCase();let s=null,a=-1;for(const n of e||[]){if(!n||n.enabled===!1||c(n)!==i.host)continue;const u=Array.isArray(n.reach)?n.reach:[];let o=u.length?-1:0;for(const h of u)h===r?o=Math.max(o,3):h.endsWith("/*")&&r.startsWith(h.slice(0,-1))?o=Math.max(o,2):h===i.host&&(o=Math.max(o,1));o>a&&(s=n,a=o)}return s}const l=t=>String(t||"").trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^[-.]+|[-.]+$/g,"").slice(0,48)||"x";function $(t,e){return`cp/${l(t)}/${l(e)}`}function C(t,e){return`${l(t)}/${l(e)}`}function R(t,e,{username:i=null}={}){const r=t||{},s=c(r);if(!s||!e)return{};const a=i||r.username||{github:"x-access-token","github-enterprise":"x-access-token",gitlab:"oauth2",bitbucket:"x-token-auth",gitea:"oauth2","azure-devops":"pat"}[r.kind]||"token",n={GIT_TERMINAL_PROMPT:"0",GIT_CONFIG_COUNT:"2",GIT_CONFIG_KEY_0:`credential.https://${s}.helper`,GIT_CONFIG_VALUE_0:'!f() { if [ "$1" = get ]; then printf "username=%s\\npassword=%s\\n" "$CHATPANEL_SCM_USER" "$CHATPANEL_SCM_TOKEN"; fi; }; f',GIT_CONFIG_KEY_1:`credential.https://${s}.useHttpPath`,GIT_CONFIG_VALUE_1:"false",CHATPANEL_SCM_USER:a,CHATPANEL_SCM_TOKEN:String(e)};return(r.kind==="github"||r.kind==="github-enterprise")&&(n.GH_TOKEN=String(e),r.kind==="github-enterprise"&&(n.GH_ENTERPRISE_TOKEN=String(e),n.GH_HOST=s)),r.kind==="gitlab"&&(n.GITLAB_TOKEN=String(e),n.GITLAB_HOST=s),n}function T(t){const e=t||{};return`${{github:"GitHub","github-enterprise":"GitHub Enterprise",gitlab:"GitLab",bitbucket:"Bitbucket",gitea:"Gitea","azure-devops":"Azure DevOps",git:"Git"}[e.kind]||e.kind} · ${c(e)}${e.reach?.length?` · ${e.reach.join(", ")}`:""}${e.enabled===!1?" (off)":""}`}function L(){return{id:"",kind:"github",label:"",baseUrl:"",reach:[],username:""}}function x(t){const e=t||{},r={id:String(e.id||"").trim()||l(e.label||`${e.kind}-${c(e)}`),kind:e.kind||"github",label:String(e.label||"").trim(),...e.baseUrl?{baseUrl:String(e.baseUrl).trim()}:{},...e.username?{username:String(e.username).trim()}:{},...e.secretRef?{secretRef:String(e.secretRef).trim()}:{},reach:String(Array.isArray(e.reach)?e.reach.join(","):e.reach||"").split(/[,\s]+/).map(a=>a.trim()).filter(Boolean),enabled:e.enabled!==!1,...e.createdAt?{createdAt:e.createdAt}:{}},s=p(r);return s.ok?{ok:!0,connection:k(r)}:{ok:!1,errors:s.errors}}export{b as CONNECTION_ID_RE,f as SCM_KINDS,d as SECRET_REF_RE,_ as ScmError,L as blankConnection,$ as branchFor,A as connectionFor,x as connectionFromForm,R as credentialEnv,T as describeConnection,c as hostOf,k as normalizeConnection,E as parseRemote,p as validateConnection,C as worktreeDirFor};
|
package/scopes.js
CHANGED
|
@@ -1,12 +1 @@
|
|
|
1
|
-
|
|
2
|
-
//
|
|
3
|
-
// One list names what anything in ChatPanel may touch. Capabilities declare `reads`
|
|
4
|
-
// and `writes` from it, sources declare `reads`, and a skill package declares `reads`
|
|
5
|
-
// — three declarations, one vocabulary, or "what may this reach" gets three answers.
|
|
6
|
-
//
|
|
7
|
-
// It is a separate module rather than a constant inside capability.js because the
|
|
8
|
-
// consumers have very different weights. The bridge vendors the skill contract and has
|
|
9
|
-
// zero runtime dependencies by design; pulling the capability machinery and the event
|
|
10
|
-
// schema behind it to reach a five-element array would be the transitive-graph mistake
|
|
11
|
-
// the extension's first-paint budget exists to prevent, one repo over.
|
|
12
|
-
export const DATA_SCOPES = Object.freeze(['notes', 'meetings', 'chats', 'memory', 'page', 'files', 'net']);
|
|
1
|
+
const e=Object.freeze(["notes","meetings","chats","memory","page","files","net"]);export{e as DATA_SCOPES};
|
package/scorecard.js
CHANGED
|
@@ -1,335 +1 @@
|
|
|
1
|
-
// An agent's scorecard — an immutable record of what it actually did, and matching on it.
|
|
2
|
-
//
|
|
3
|
-
// A scorecard is a CHAIN of entries, one per fact, append-only: a task it finished (how big,
|
|
4
|
-
// with which tools, alongside whom, in which role), a rating a job gave it, an agent it
|
|
5
|
-
// created, an interaction. Every entry carries the hash of the one before and its own hash,
|
|
6
|
-
// so an edit anywhere breaks every link after it; the gateway's store adds its own mark
|
|
7
|
-
// (an HMAC over the hash with a key only the store holds) so an entry a client — or an
|
|
8
|
-
// agent — wrote for itself shows as unattested. The facts are produced by the runner and
|
|
9
|
-
// attested by the store, never written by the agent: the only way to a better scorecard is
|
|
10
|
-
// the work.
|
|
11
|
-
//
|
|
12
|
-
// `summarize` turns the chain into the card a recruiter reads; `fit` scores an agent type
|
|
13
|
-
// against a job's needs with that record — the same function the evaluator starts from, so
|
|
14
|
-
// an application's fit has reasons a person can read and overrule.
|
|
15
|
-
//
|
|
16
|
-
// The model is a variable, not a constant (architecture-pillars.md §13): every task fact also
|
|
17
|
-
// says which ENGINE did it — a model endpoint or a harness (a CLI coding agent), and for a
|
|
18
|
-
// harness the model it was asked to run — so `summarize` can split the card by engine
|
|
19
|
-
// (`byEngine`) and a recruiter can tell an agent that did well on a small model from one
|
|
20
|
-
// that was carried by a large one. And where the task ran in a git checkout (§14), the fact
|
|
21
|
-
// carries `scm`: the branch and HEAD before and after, commits made, a PR when one was
|
|
22
|
-
// opened, and whether it merged — the one outcome that does not come from a judge.
|
|
23
|
-
//
|
|
24
|
-
// Dependency-free: hashing is `crypto.subtle` (browser, Node, a phone), injectable for tests.
|
|
25
|
-
|
|
26
|
-
export const SCORECARD_ENTRY_KINDS = Object.freeze(['task.done', 'task.failed', 'rating', 'created', 'interaction', 'role']);
|
|
27
|
-
export const ROLE_KINDS = Object.freeze(['ic', 'orchestrator', 'manager', 'manager-of-managers']);
|
|
28
|
-
export const SCORECARD_VERSION = 1;
|
|
29
|
-
export const ENGINE_KINDS = Object.freeze(['model', 'harness']);
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* An engine as the record keeps it: `{ kind, id, model?, label? }`. `kind` is `model` (an
|
|
33
|
-
* endpoint the client calls) or `harness` (a CLI coding agent the bridge runs — `id` is the
|
|
34
|
-
* harness, `model` the model it was asked to run, when one was named). A host that does not
|
|
35
|
-
* say the kind gets `model`, which is the honest default for a bare model id. A string is
|
|
36
|
-
* an id.
|
|
37
|
-
*/
|
|
38
|
-
export function normalizeEngine(e) {
|
|
39
|
-
if (!e) return null;
|
|
40
|
-
const src = typeof e === 'string' ? { id: e } : e;
|
|
41
|
-
const id = String(src.id || src.harnessId || src.model || '').trim();
|
|
42
|
-
if (!id) return null;
|
|
43
|
-
const kind = ENGINE_KINDS.includes(src.kind) ? src.kind : (src.harnessId ? 'harness' : 'model');
|
|
44
|
-
const model = src.model != null && String(src.model).trim() && String(src.model) !== id ? String(src.model).trim() : undefined;
|
|
45
|
-
return { kind, id, ...(model ? { model } : {}), ...(src.label && String(src.label) !== id ? { label: String(src.label).slice(0, 120) } : {}) };
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** One key per engine — what `byEngine` groups on and what the model ledger will be keyed by. */
|
|
49
|
-
export function engineKey(e) {
|
|
50
|
-
const n = normalizeEngine(e);
|
|
51
|
-
return n ? `${n.kind}:${n.id}${n.model ? `/${n.model}` : ''}` : null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/** What a task did in a checkout, as the record keeps it. Strings clipped, counts rounded. */
|
|
55
|
-
export function normalizeScm(s) {
|
|
56
|
-
if (!s || typeof s !== 'object') return null;
|
|
57
|
-
const str = (v, n = 200) => (v == null || v === '' ? undefined : String(v).slice(0, n));
|
|
58
|
-
const out = {
|
|
59
|
-
repo: str(s.repo, 300), remote: str(s.remote, 300), base: str(s.base, 120), branch: str(s.branch, 120),
|
|
60
|
-
head: str(s.head, 64), headAfter: str(s.headAfter, 64),
|
|
61
|
-
commits: s.commits != null ? Math.max(0, Math.round(Number(s.commits) || 0)) : undefined,
|
|
62
|
-
pr: str(s.pr, 300),
|
|
63
|
-
merged: s.merged === true ? true : s.merged === false ? false : undefined,
|
|
64
|
-
dirty: s.dirty === true ? true : s.dirty === false ? false : undefined,
|
|
65
|
-
};
|
|
66
|
-
for (const k of Object.keys(out)) if (out[k] === undefined) delete out[k];
|
|
67
|
-
return Object.keys(out).length ? out : null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const enc = new TextEncoder();
|
|
71
|
-
const hex = (buf) => [...new Uint8Array(buf)].map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
72
|
-
|
|
73
|
-
/** Canonical JSON: keys sorted at every level, so the same fact hashes the same everywhere. */
|
|
74
|
-
export function canonical(v) {
|
|
75
|
-
if (v === null || typeof v !== 'object') return JSON.stringify(v);
|
|
76
|
-
if (Array.isArray(v)) return `[${v.map(canonical).join(',')}]`;
|
|
77
|
-
return `{${Object.keys(v).sort().map((k) => (v[k] === undefined ? null : `${JSON.stringify(k)}:${canonical(v[k])}`)).filter(Boolean).join(',')}}`;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
/** SHA-256 over text, as hex; `subtle` is injectable (a runtime without it passes its own). */
|
|
81
|
-
export async function sha256(text, { subtle = globalThis.crypto?.subtle } = {}) {
|
|
82
|
-
if (!subtle) throw new Error('scorecard: no crypto.subtle — pass one');
|
|
83
|
-
return hex(await subtle.digest('SHA-256', enc.encode(String(text))));
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** The fields a hash covers — everything but the hash and the store's mark. */
|
|
87
|
-
function hashable(e) {
|
|
88
|
-
const { hash: _h, sig: _s, ...rest } = e;
|
|
89
|
-
return rest;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* A new entry chained onto `prev` (the last entry, or null for the first). Pure apart from
|
|
94
|
-
* the digest: the caller (the store) decides whether it is attested.
|
|
95
|
-
*/
|
|
96
|
-
export async function makeEntry(fact, prev, { now = () => Date.now(), subtle } = {}) {
|
|
97
|
-
if (!fact || typeof fact !== 'object') throw new Error('scorecard: an entry needs a fact');
|
|
98
|
-
if (!SCORECARD_ENTRY_KINDS.includes(fact.kind)) throw new Error(`scorecard: kind must be one of ${SCORECARD_ENTRY_KINDS.join(', ')}`);
|
|
99
|
-
if (!fact.agentId) throw new Error('scorecard: agentId required');
|
|
100
|
-
const e = {
|
|
101
|
-
v: SCORECARD_VERSION,
|
|
102
|
-
seq: prev ? prev.seq + 1 : 0,
|
|
103
|
-
agentId: String(fact.agentId),
|
|
104
|
-
kind: fact.kind,
|
|
105
|
-
at: Number(fact.at) || now(),
|
|
106
|
-
...(fact.projectId ? { projectId: String(fact.projectId) } : {}),
|
|
107
|
-
...(fact.jobId ? { jobId: String(fact.jobId) } : {}),
|
|
108
|
-
...(fact.runId ? { runId: String(fact.runId) } : {}),
|
|
109
|
-
...(fact.taskId ? { taskId: String(fact.taskId) } : {}),
|
|
110
|
-
...(fact.model ? { model: String(fact.model) } : {}),
|
|
111
|
-
...(normalizeEngine(fact.engine) ? { engine: normalizeEngine(fact.engine) } : {}),
|
|
112
|
-
...(normalizeScm(fact.scm) ? { scm: normalizeScm(fact.scm) } : {}),
|
|
113
|
-
...(fact.size ? { size: sizeOf(fact.size) } : {}),
|
|
114
|
-
...(fact.roleKind ? { roleKind: ROLE_KINDS.includes(fact.roleKind) ? fact.roleKind : 'ic' } : {}),
|
|
115
|
-
...(Array.isArray(fact.tools) && fact.tools.length ? { tools: [...new Set(fact.tools.map(String))].sort() } : {}),
|
|
116
|
-
...(Array.isArray(fact.with) && fact.with.length ? { with: [...new Set(fact.with.map(String))].sort() } : {}),
|
|
117
|
-
...(Array.isArray(fact.created) && fact.created.length ? { created: [...new Set(fact.created.map(String))] } : {}),
|
|
118
|
-
...(fact.rating ? { rating: { by: String(fact.rating.by || 'person'), score: clamp01(fact.rating.score), ...(fact.rating.note ? { note: String(fact.rating.note).slice(0, 500) } : {}), ...(fact.rating.about != null ? { about: Number(fact.rating.about) } : {}) } } : {}),
|
|
119
|
-
...(Array.isArray(fact.refs) && fact.refs.length ? { refs: fact.refs.map(String).slice(0, 12) } : {}),
|
|
120
|
-
...(fact.error ? { error: String(fact.error).slice(0, 300) } : {}),
|
|
121
|
-
prev: prev ? prev.hash : null,
|
|
122
|
-
};
|
|
123
|
-
e.hash = await sha256(canonical(hashable(e)), { subtle });
|
|
124
|
-
return e;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
const clamp01 = (n) => Math.max(0, Math.min(1, Number(n) || 0));
|
|
128
|
-
const r3 = (v) => (v == null ? null : Math.round(v * 1000) / 1000);
|
|
129
|
-
const sizeOf = (s) => ({ ms: Math.max(0, Math.round(Number(s.ms) || 0)), steps: Math.max(0, Math.round(Number(s.steps) || 0)), tools: Math.max(0, Math.round(Number(s.tools) || 0)), findings: Math.max(0, Math.round(Number(s.findings) || 0)), tokens: Math.max(0, Math.round(Number(s.tokens) || 0)) });
|
|
130
|
-
|
|
131
|
-
/** Does every link hold? Returns `{ ok, at }` — `at` is the seq of the first broken entry. */
|
|
132
|
-
export async function verifyChain(entries, { subtle } = {}) {
|
|
133
|
-
let prev = null;
|
|
134
|
-
for (const e of entries || []) {
|
|
135
|
-
if (!e || typeof e !== 'object') return { ok: false, at: prev ? prev.seq + 1 : 0, why: 'not an entry' };
|
|
136
|
-
if ((prev ? prev.seq + 1 : 0) !== e.seq) return { ok: false, at: e.seq, why: 'seq' };
|
|
137
|
-
if ((prev ? prev.hash : null) !== e.prev) return { ok: false, at: e.seq, why: 'prev' };
|
|
138
|
-
const h = await sha256(canonical(hashable(e)), { subtle });
|
|
139
|
-
if (h !== e.hash) return { ok: false, at: e.seq, why: 'hash' };
|
|
140
|
-
prev = e;
|
|
141
|
-
}
|
|
142
|
-
return { ok: true, at: null, length: (entries || []).length };
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* The store's mark. `key` is raw bytes only the store holds; an HMAC-SHA-256 over the hash.
|
|
147
|
-
* Anyone can re-hash the chain; only the store can mark it, so an entry written elsewhere
|
|
148
|
-
* is honest about being unattested. Injectable `subtle` again.
|
|
149
|
-
*/
|
|
150
|
-
export async function attest(entry, key, { subtle = globalThis.crypto?.subtle } = {}) {
|
|
151
|
-
const k = await subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign']);
|
|
152
|
-
return { ...entry, sig: hex(await subtle.sign('HMAC', k, enc.encode(entry.hash))) };
|
|
153
|
-
}
|
|
154
|
-
export async function verifyAttested(entries, key, { subtle = globalThis.crypto?.subtle } = {}) {
|
|
155
|
-
const k = await subtle.importKey('raw', key, { name: 'HMAC', hash: 'SHA-256' }, false, ['verify']);
|
|
156
|
-
const out = [];
|
|
157
|
-
for (const e of entries || []) {
|
|
158
|
-
const sig = e?.sig ? new Uint8Array(e.sig.match(/../g).map((x) => parseInt(x, 16))) : null;
|
|
159
|
-
out.push(!!sig && await subtle.verify('HMAC', k, sig, enc.encode(e.hash)));
|
|
160
|
-
}
|
|
161
|
-
return { ok: out.every(Boolean), attested: out.filter(Boolean).length, of: out.length };
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/** The card a recruiter reads. */
|
|
165
|
-
export function summarize(entries, { recent = 5 } = {}) {
|
|
166
|
-
const list = (entries || []).filter((e) => e && e.kind);
|
|
167
|
-
const done = list.filter((e) => e.kind === 'task.done');
|
|
168
|
-
const failed = list.filter((e) => e.kind === 'task.failed');
|
|
169
|
-
const sum = (k) => done.reduce((n, e) => n + (e.size?.[k] || 0), 0);
|
|
170
|
-
const largest = done.reduce((m, e) => Math.max(m, e.size?.steps || 0), 0);
|
|
171
|
-
const tools = new Set(); const withAgents = new Set(); const created = new Set();
|
|
172
|
-
const roles = { ic: 0, orchestrator: 0, manager: 0, 'manager-of-managers': 0 };
|
|
173
|
-
const models = new Map();
|
|
174
|
-
// Per engine: how many tasks, how they went, how big, and the ratings that were ABOUT a
|
|
175
|
-
// task on it. A rating names its task by `about` (the seq of the entry it rates), by
|
|
176
|
-
// taskId, or — failing both — by runId when that run had exactly one task on the card.
|
|
177
|
-
const engines = new Map(); // key -> { engine, tasks, done, failed, tokens, ratings[] }
|
|
178
|
-
const engineOfEntry = new Map(); // seq -> key
|
|
179
|
-
const byTask = new Map(); const byRun = new Map(); // taskId -> seq, runId -> [seq]
|
|
180
|
-
const scm = { tasks: 0, commits: 0, prs: 0, merged: 0 };
|
|
181
|
-
for (const e of list) {
|
|
182
|
-
for (const t of e.tools || []) tools.add(t);
|
|
183
|
-
for (const a of e.with || []) withAgents.add(a);
|
|
184
|
-
for (const a of e.created || []) created.add(a);
|
|
185
|
-
if (e.roleKind && (e.kind === 'task.done' || e.kind === 'task.failed' || e.kind === 'role')) roles[e.roleKind] = (roles[e.roleKind] || 0) + 1;
|
|
186
|
-
if (e.model) models.set(e.model, (models.get(e.model) || 0) + 1);
|
|
187
|
-
if (e.kind === 'task.done' || e.kind === 'task.failed') {
|
|
188
|
-
const key = engineKey(e.engine);
|
|
189
|
-
if (key) {
|
|
190
|
-
const row = engines.get(key) || { engine: normalizeEngine(e.engine), tasks: 0, done: 0, failed: 0, tokens: 0, ratings: [] };
|
|
191
|
-
row.tasks += 1; row[e.kind === 'task.done' ? 'done' : 'failed'] += 1; row.tokens += e.size?.tokens || 0;
|
|
192
|
-
engines.set(key, row);
|
|
193
|
-
engineOfEntry.set(e.seq, key);
|
|
194
|
-
if (e.taskId) byTask.set(`${e.runId || ''}/${e.taskId}`, e.seq);
|
|
195
|
-
if (e.runId) byRun.set(e.runId, [...(byRun.get(e.runId) || []), e.seq]);
|
|
196
|
-
}
|
|
197
|
-
if (e.scm) { scm.tasks += 1; scm.commits += e.scm.commits || 0; if (e.scm.pr) scm.prs += 1; if (e.scm.merged) scm.merged += 1; }
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
for (const e of list) {
|
|
201
|
-
if (e.kind !== 'rating' || !e.rating) continue;
|
|
202
|
-
const seq = e.rating.about != null ? e.rating.about
|
|
203
|
-
: e.taskId && byTask.has(`${e.runId || ''}/${e.taskId}`) ? byTask.get(`${e.runId || ''}/${e.taskId}`)
|
|
204
|
-
: e.runId && (byRun.get(e.runId) || []).length === 1 ? byRun.get(e.runId)[0] : null;
|
|
205
|
-
const key = seq != null ? engineOfEntry.get(seq) : null;
|
|
206
|
-
if (key) engines.get(key).ratings.push(e.rating.score);
|
|
207
|
-
}
|
|
208
|
-
const mean = (xs) => (xs.length ? xs.reduce((a, b) => a + b, 0) / xs.length : null);
|
|
209
|
-
const byEngine = [...engines.values()].sort((a, b) => b.tasks - a.tasks).map((r) => ({
|
|
210
|
-
key: engineKey(r.engine), ...r.engine, tasks: r.tasks, done: r.done, failed: r.failed,
|
|
211
|
-
failRate: r.tasks ? Math.round((r.failed / r.tasks) * 1000) / 1000 : 0,
|
|
212
|
-
tokens: r.tasks ? Math.round(r.tokens / r.tasks) : 0, // mean per task — the cost proxy until the ledger prices it
|
|
213
|
-
rating: { avg: mean(r.ratings), count: r.ratings.length },
|
|
214
|
-
}));
|
|
215
|
-
// 1 − spread of rating across engines it was rated on (≥ 2): low spread = robust to routing;
|
|
216
|
-
// high spread = it NEEDS a particular engine, a fact a recruiter respects, not a penalty.
|
|
217
|
-
const ratedEngines = byEngine.filter((r) => r.rating.avg != null).map((r) => r.rating.avg);
|
|
218
|
-
const engineIndependence = ratedEngines.length >= 2 ? Math.round((1 - (Math.max(...ratedEngines) - Math.min(...ratedEngines))) * 1000) / 1000 : null;
|
|
219
|
-
// Leverage (rating above the engine's own mean) and efficiency (rating ÷ cost) wait on the
|
|
220
|
-
// model ledger (§13.2, with A1): they need every engine's mean, which one card cannot know.
|
|
221
|
-
const ratings = list.filter((e) => e.kind === 'rating' && e.rating).map((e) => e.rating.score);
|
|
222
|
-
const avg = ratings.length ? ratings.reduce((a, b) => a + b, 0) / ratings.length : null;
|
|
223
|
-
const recentRatings = ratings.slice(-recent);
|
|
224
|
-
const refs = [...new Set(list.flatMap((e) => e.refs || []))].slice(-recent);
|
|
225
|
-
return {
|
|
226
|
-
agentId: list[0]?.agentId || null,
|
|
227
|
-
entries: list.length,
|
|
228
|
-
jobsDone: done.length,
|
|
229
|
-
jobsFailed: failed.length,
|
|
230
|
-
size: { ms: sum('ms'), steps: sum('steps'), tools: sum('tools'), findings: sum('findings'), tokens: sum('tokens'), largestSteps: largest },
|
|
231
|
-
tools: [...tools].sort(),
|
|
232
|
-
workedWith: [...withAgents].sort(),
|
|
233
|
-
created: [...created],
|
|
234
|
-
roles,
|
|
235
|
-
models: [...models.entries()].sort((a, b) => b[1] - a[1]).map(([m, n]) => ({ model: m, tasks: n })),
|
|
236
|
-
byEngine,
|
|
237
|
-
engineIndependence,
|
|
238
|
-
scm,
|
|
239
|
-
rating: { avg, count: ratings.length, recent: recentRatings.length ? recentRatings.reduce((a, b) => a + b, 0) / recentRatings.length : null },
|
|
240
|
-
refs,
|
|
241
|
-
since: list[0]?.at || null,
|
|
242
|
-
last: list.at(-1)?.at || null,
|
|
243
|
-
head: list.at(-1)?.hash || null,
|
|
244
|
-
};
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
/**
|
|
248
|
-
* How well an agent TYPE fits a job, with that type's record. `job.needs` is
|
|
249
|
-
* `{ skills[], tools[], grants[] }`; `type` carries `skills[]`, `tools[]`, `grants[]`; the
|
|
250
|
-
* summary is `summarize()`'s. Returns `{ score, reasons }` in [0, 1] — needs first (a type
|
|
251
|
-
* without the tools cannot do the job), track record second, size third.
|
|
252
|
-
*/
|
|
253
|
-
export function fit(job, type, summary = null, { qualityOf = null, costOf = null, adjust = true } = {}) {
|
|
254
|
-
const needs = job?.needs || {};
|
|
255
|
-
const have = (xs) => new Set((xs || []).map((x) => String(x).toLowerCase()));
|
|
256
|
-
const skills = have(type?.skills); const tools = have(type?.tools); const grants = have(type?.grants);
|
|
257
|
-
const reasons = [];
|
|
258
|
-
const coverage = (want, has, label) => {
|
|
259
|
-
const w = (want || []).map((x) => String(x).toLowerCase());
|
|
260
|
-
if (!w.length) return 1;
|
|
261
|
-
const hit = w.filter((x) => has.has(x));
|
|
262
|
-
if (hit.length < w.length) reasons.push(`missing ${label}: ${w.filter((x) => !has.has(x)).join(', ')}`);
|
|
263
|
-
return hit.length / w.length;
|
|
264
|
-
};
|
|
265
|
-
const cSkills = coverage(needs.skills, skills, 'skills');
|
|
266
|
-
const cTools = coverage(needs.tools, tools, 'tools');
|
|
267
|
-
const cGrants = coverage(needs.grants, grants, 'grants');
|
|
268
|
-
const needScore = (cSkills * 0.5 + cTools * 0.3 + cGrants * 0.2);
|
|
269
|
-
if (needScore === 1) reasons.push('has every skill, tool and grant the job names');
|
|
270
|
-
let record = 0.5; // a fresh type is neither trusted nor distrusted
|
|
271
|
-
let adjusted = null;
|
|
272
|
-
if (summary && summary.entries) {
|
|
273
|
-
const doneRate = summary.jobsDone + summary.jobsFailed ? summary.jobsDone / (summary.jobsDone + summary.jobsFailed) : 0.5;
|
|
274
|
-
// The track record uses the MODEL-ADJUSTED rating when the caller can say what the
|
|
275
|
-
// engines were worth (§13.3): an agent rated 0.82 mostly on a weak engine ranks above
|
|
276
|
-
// one rated 0.82 on a frontier model. The reasons say so, and a person can turn it off.
|
|
277
|
-
adjusted = adjust && qualityOf && summary.rating.avg != null ? adjustSummary(summary, { qualityOf, costOf: costOf || undefined }) : null;
|
|
278
|
-
const rated = summary.rating.avg == null ? 0.5 : (adjusted?.adjusted ?? summary.rating.avg);
|
|
279
|
-
record = doneRate * 0.5 + rated * 0.5;
|
|
280
|
-
reasons.push(`${summary.jobsDone} done, ${summary.jobsFailed} failed${summary.rating.avg != null ? (adjusted && adjusted.adjusted !== adjusted.raw ? `, rated ${Math.round(adjusted.raw * 100)}% raw, ${Math.round(adjusted.adjusted * 100)}% adjusted — ${adjusted.basis[0] || 'engine-corrected'}` : `, rated ${Math.round(summary.rating.avg * 100)}%`) : ''}`);
|
|
281
|
-
if (adjusted?.leverage != null && adjusted.leverage > 0.05) reasons.push(`adds ${adjusted.leverage} over its engines' own quality`);
|
|
282
|
-
if (adjusted?.efficiency) reasons.push(`cleared the bar cheapest on ${adjusted.efficiency.engine}`);
|
|
283
|
-
if (summary.roles.orchestrator + summary.roles.manager + summary.roles['manager-of-managers'] > 0) reasons.push(`has led: ${summary.roles.orchestrator} as orchestrator, ${summary.roles.manager} as manager`);
|
|
284
|
-
} else {
|
|
285
|
-
reasons.push('no record yet');
|
|
286
|
-
}
|
|
287
|
-
const wantSize = Number(job?.size?.steps) || 0;
|
|
288
|
-
const sizeScore = !wantSize ? 1 : Math.min(1, (summary?.size?.largestSteps || 0) / wantSize) * 0.5 + 0.5;
|
|
289
|
-
if (wantSize && (summary?.size?.largestSteps || 0) < wantSize) reasons.push(`largest task so far ${summary?.size?.largestSteps || 0} steps; this one is ~${wantSize}`);
|
|
290
|
-
const score = Math.round((needScore * 0.6 + record * 0.3 + sizeScore * 0.1) * 1000) / 1000;
|
|
291
|
-
return { score, reasons, parts: { needs: needScore, record, size: sizeScore }, ...(adjusted ? { adjusted } : {}) };
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// ── Agent scores, normalised by engine (§13.3) — what the model ledger's cards make possible ──
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* The model-adjusted view of an agent's card (scorecard.js `summarize()`), given what its
|
|
298
|
-
* engines are worth: `qualityOf(key)` → the engine's quality in [0, 1] (the card's mean
|
|
299
|
-
* rating for the job kind when observed, else the router's guess) or null when unknown.
|
|
300
|
-
*
|
|
301
|
-
* leverage rating on an engine minus that engine's quality, weighted by tasks — what
|
|
302
|
-
* the agent's prompt and tools add that the model does not supply on its own
|
|
303
|
-
* adjusted the raw rating corrected for the engines it ran on: work done on a weak
|
|
304
|
-
* engine counts for more, on a strong one for less; `k` bounds the correction
|
|
305
|
-
* efficiency adjusted rating ÷ cost per task on the cheapest engine that cleared `bar`
|
|
306
|
-
* (`costOf(key)` → $/task or a token proxy; null when nothing is priced)
|
|
307
|
-
*
|
|
308
|
-
* Returns `{ raw, adjusted, leverage, efficiency, basis[] }` with `basis` the reasons a
|
|
309
|
-
* person reads ("60 % of its tasks ran on a 0.3-quality engine").
|
|
310
|
-
*/
|
|
311
|
-
export function adjustSummary(summary, { qualityOf = () => null, costOf = () => null, reference = 0.6, k = 0.3, bar = 0.5 } = {}) {
|
|
312
|
-
const raw = summary?.rating?.avg ?? null;
|
|
313
|
-
const rows = (summary?.byEngine || []).filter((r) => r.key);
|
|
314
|
-
const known = rows.map((r) => ({ ...r, quality: qualityOf(r.key) })).filter((r) => Number.isFinite(r.quality));
|
|
315
|
-
const totalTasks = known.reduce((n, r) => n + r.tasks, 0);
|
|
316
|
-
const basis = [];
|
|
317
|
-
if (raw == null || !known.length || !totalTasks) return { raw, adjusted: raw, leverage: null, efficiency: null, basis: raw == null ? ['not rated yet'] : ['engines not rated yet — raw rating used'] };
|
|
318
|
-
// Correction: how far below the reference the engines it ran on sit, task-weighted.
|
|
319
|
-
const correction = k * known.reduce((s, r) => s + (r.tasks / totalTasks) * (reference - r.quality), 0);
|
|
320
|
-
const adjusted = Math.max(0, Math.min(1, raw + correction));
|
|
321
|
-
const weak = known.filter((r) => r.quality < reference);
|
|
322
|
-
if (weak.length) basis.push(`${Math.round((weak.reduce((n, r) => n + r.tasks, 0) / totalTasks) * 100)} % of its tasks ran on ${weak.length === 1 ? `a ${weak[0].quality}-quality engine` : 'engines below the reference'}`);
|
|
323
|
-
const strong = known.filter((r) => r.quality > reference);
|
|
324
|
-
if (strong.length && !weak.length) basis.push(`ran on engines above the reference (${strong.map((r) => r.quality).join(', ')})`);
|
|
325
|
-
// Leverage over the engines it was rated on.
|
|
326
|
-
const rated = known.filter((r) => r.rating?.avg != null);
|
|
327
|
-
const ratedTasks = rated.reduce((n, r) => n + r.rating.count, 0);
|
|
328
|
-
const leverage = ratedTasks ? r3(rated.reduce((s, r) => s + (r.rating.count / ratedTasks) * (r.rating.avg - r.quality), 0)) : null;
|
|
329
|
-
if (leverage != null) basis.push(`${leverage >= 0 ? '+' : ''}${leverage} over its engines' own quality`);
|
|
330
|
-
// Efficiency on the cheapest engine that cleared the bar.
|
|
331
|
-
const cleared = rated.filter((r) => r.rating.avg >= bar).map((r) => ({ ...r, cost: costOf(r.key) ?? (r.tokens || null) })).filter((r) => r.cost != null && r.cost > 0).sort((a, b) => a.cost - b.cost);
|
|
332
|
-
const efficiency = cleared.length ? { value: r3(adjusted / cleared[0].cost), engine: cleared[0].key, costPerTask: cleared[0].cost } : null;
|
|
333
|
-
return { raw: r3(raw), adjusted: r3(adjusted), leverage, efficiency, basis };
|
|
334
|
-
}
|
|
335
|
-
|
|
1
|
+
const E=Object.freeze(["task.done","task.failed","rating","created","interaction","role"]),T=Object.freeze(["ic","orchestrator","manager","manager-of-managers"]),H=1,_=Object.freeze(["model","harness"]);function A(e){if(!e)return null;const s=typeof e=="string"?{id:e}:e,t=String(s.id||s.harnessId||s.model||"").trim();if(!t)return null;const r=_.includes(s.kind)?s.kind:s.harnessId?"harness":"model",d=s.model!=null&&String(s.model).trim()&&String(s.model)!==t?String(s.model).trim():void 0;return{kind:r,id:t,...d?{model:d}:{},...s.label&&String(s.label)!==t?{label:String(s.label).slice(0,120)}:{}}}function O(e){const s=A(e);return s?`${s.kind}:${s.id}${s.model?`/${s.model}`:""}`:null}function K(e){if(!e||typeof e!="object")return null;const s=(r,d=200)=>r==null||r===""?void 0:String(r).slice(0,d),t={repo:s(e.repo,300),remote:s(e.remote,300),base:s(e.base,120),branch:s(e.branch,120),head:s(e.head,64),headAfter:s(e.headAfter,64),commits:e.commits!=null?Math.max(0,Math.round(Number(e.commits)||0)):void 0,pr:s(e.pr,300),merged:e.merged===!0?!0:e.merged===!1?!1:void 0,dirty:e.dirty===!0?!0:e.dirty===!1?!1:void 0};for(const r of Object.keys(t))t[r]===void 0&&delete t[r];return Object.keys(t).length?t:null}const N=new TextEncoder,C=e=>[...new Uint8Array(e)].map(s=>s.toString(16).padStart(2,"0")).join("");function q(e){return e===null||typeof e!="object"?JSON.stringify(e):Array.isArray(e)?`[${e.map(q).join(",")}]`:`{${Object.keys(e).sort().map(s=>e[s]===void 0?null:`${JSON.stringify(s)}:${q(e[s])}`).filter(Boolean).join(",")}}`}async function D(e,{subtle:s=globalThis.crypto?.subtle}={}){if(!s)throw new Error("scorecard: no crypto.subtle — pass one");return C(await s.digest("SHA-256",N.encode(String(e))))}function R(e){const{hash:s,sig:t,...r}=e;return r}async function G(e,s,{now:t=()=>Date.now(),subtle:r}={}){if(!e||typeof e!="object")throw new Error("scorecard: an entry needs a fact");if(!E.includes(e.kind))throw new Error(`scorecard: kind must be one of ${E.join(", ")}`);if(!e.agentId)throw new Error("scorecard: agentId required");const d={v:H,seq:s?s.seq+1:0,agentId:String(e.agentId),kind:e.kind,at:Number(e.at)||t(),...e.projectId?{projectId:String(e.projectId)}:{},...e.jobId?{jobId:String(e.jobId)}:{},...e.runId?{runId:String(e.runId)}:{},...e.taskId?{taskId:String(e.taskId)}:{},...e.model?{model:String(e.model)}:{},...A(e.engine)?{engine:A(e.engine)}:{},...K(e.scm)?{scm:K(e.scm)}:{},...e.size?{size:B(e.size)}:{},...e.roleKind?{roleKind:T.includes(e.roleKind)?e.roleKind:"ic"}:{},...Array.isArray(e.tools)&&e.tools.length?{tools:[...new Set(e.tools.map(String))].sort()}:{},...Array.isArray(e.with)&&e.with.length?{with:[...new Set(e.with.map(String))].sort()}:{},...Array.isArray(e.created)&&e.created.length?{created:[...new Set(e.created.map(String))]}:{},...e.rating?{rating:{by:String(e.rating.by||"person"),score:F(e.rating.score),...e.rating.note?{note:String(e.rating.note).slice(0,500)}:{},...e.rating.about!=null?{about:Number(e.rating.about)}:{}}}:{},...Array.isArray(e.refs)&&e.refs.length?{refs:e.refs.map(String).slice(0,12)}:{},...e.error?{error:String(e.error).slice(0,300)}:{},prev:s?s.hash:null};return d.hash=await D(q(R(d)),{subtle:r}),d}const F=e=>Math.max(0,Math.min(1,Number(e)||0)),v=e=>e==null?null:Math.round(e*1e3)/1e3,B=e=>({ms:Math.max(0,Math.round(Number(e.ms)||0)),steps:Math.max(0,Math.round(Number(e.steps)||0)),tools:Math.max(0,Math.round(Number(e.tools)||0)),findings:Math.max(0,Math.round(Number(e.findings)||0)),tokens:Math.max(0,Math.round(Number(e.tokens)||0))});async function J(e,{subtle:s}={}){let t=null;for(const r of e||[]){if(!r||typeof r!="object")return{ok:!1,at:t?t.seq+1:0,why:"not an entry"};if((t?t.seq+1:0)!==r.seq)return{ok:!1,at:r.seq,why:"seq"};if((t?t.hash:null)!==r.prev)return{ok:!1,at:r.seq,why:"prev"};if(await D(q(R(r)),{subtle:s})!==r.hash)return{ok:!1,at:r.seq,why:"hash"};t=r}return{ok:!0,at:null,length:(e||[]).length}}async function U(e,s,{subtle:t=globalThis.crypto?.subtle}={}){const r=await t.importKey("raw",s,{name:"HMAC",hash:"SHA-256"},!1,["sign"]);return{...e,sig:C(await t.sign("HMAC",r,N.encode(e.hash)))}}async function P(e,s,{subtle:t=globalThis.crypto?.subtle}={}){const r=await t.importKey("raw",s,{name:"HMAC",hash:"SHA-256"},!1,["verify"]),d=[];for(const h of e||[]){const c=h?.sig?new Uint8Array(h.sig.match(/../g).map(j=>parseInt(j,16))):null;d.push(!!c&&await t.verify("HMAC",r,c,N.encode(h.hash)))}return{ok:d.every(Boolean),attested:d.filter(Boolean).length,of:d.length}}function V(e,{recent:s=5}={}){const t=(e||[]).filter(n=>n&&n.kind),r=t.filter(n=>n.kind==="task.done"),d=t.filter(n=>n.kind==="task.failed"),h=n=>r.reduce((i,g)=>i+(g.size?.[n]||0),0),c=r.reduce((n,i)=>Math.max(n,i.size?.steps||0),0),j=new Set,k=new Set,M=new Set,b={ic:0,orchestrator:0,manager:0,"manager-of-managers":0},u=new Map,p=new Map,w=new Map,$=new Map,y=new Map,f={tasks:0,commits:0,prs:0,merged:0};for(const n of t){for(const i of n.tools||[])j.add(i);for(const i of n.with||[])k.add(i);for(const i of n.created||[])M.add(i);if(n.roleKind&&(n.kind==="task.done"||n.kind==="task.failed"||n.kind==="role")&&(b[n.roleKind]=(b[n.roleKind]||0)+1),n.model&&u.set(n.model,(u.get(n.model)||0)+1),n.kind==="task.done"||n.kind==="task.failed"){const i=O(n.engine);if(i){const g=p.get(i)||{engine:A(n.engine),tasks:0,done:0,failed:0,tokens:0,ratings:[]};g.tasks+=1,g[n.kind==="task.done"?"done":"failed"]+=1,g.tokens+=n.size?.tokens||0,p.set(i,g),w.set(n.seq,i),n.taskId&&$.set(`${n.runId||""}/${n.taskId}`,n.seq),n.runId&&y.set(n.runId,[...y.get(n.runId)||[],n.seq])}n.scm&&(f.tasks+=1,f.commits+=n.scm.commits||0,n.scm.pr&&(f.prs+=1),n.scm.merged&&(f.merged+=1))}}for(const n of t){if(n.kind!=="rating"||!n.rating)continue;const i=n.rating.about!=null?n.rating.about:n.taskId&&$.has(`${n.runId||""}/${n.taskId}`)?$.get(`${n.runId||""}/${n.taskId}`):n.runId&&(y.get(n.runId)||[]).length===1?y.get(n.runId)[0]:null,g=i!=null?w.get(i):null;g&&p.get(g).ratings.push(n.rating.score)}const S=n=>n.length?n.reduce((i,g)=>i+g,0)/n.length:null,a=[...p.values()].sort((n,i)=>i.tasks-n.tasks).map(n=>({key:O(n.engine),...n.engine,tasks:n.tasks,done:n.done,failed:n.failed,failRate:n.tasks?Math.round(n.failed/n.tasks*1e3)/1e3:0,tokens:n.tasks?Math.round(n.tokens/n.tasks):0,rating:{avg:S(n.ratings),count:n.ratings.length}})),m=a.filter(n=>n.rating.avg!=null).map(n=>n.rating.avg),o=m.length>=2?Math.round((1-(Math.max(...m)-Math.min(...m)))*1e3)/1e3:null,l=t.filter(n=>n.kind==="rating"&&n.rating).map(n=>n.rating.score),x=l.length?l.reduce((n,i)=>n+i,0)/l.length:null,I=l.slice(-s),z=[...new Set(t.flatMap(n=>n.refs||[]))].slice(-s);return{agentId:t[0]?.agentId||null,entries:t.length,jobsDone:r.length,jobsFailed:d.length,size:{ms:h("ms"),steps:h("steps"),tools:h("tools"),findings:h("findings"),tokens:h("tokens"),largestSteps:c},tools:[...j].sort(),workedWith:[...k].sort(),created:[...M],roles:b,models:[...u.entries()].sort((n,i)=>i[1]-n[1]).map(([n,i])=>({model:n,tasks:i})),byEngine:a,engineIndependence:o,scm:f,rating:{avg:x,count:l.length,recent:I.length?I.reduce((n,i)=>n+i,0)/I.length:null},refs:z,since:t[0]?.at||null,last:t.at(-1)?.at||null,head:t.at(-1)?.hash||null}}function W(e,s,t=null,{qualityOf:r=null,costOf:d=null,adjust:h=!0}={}){const c=e?.needs||{},j=x=>new Set((x||[]).map(I=>String(I).toLowerCase())),k=j(s?.skills),M=j(s?.tools),b=j(s?.grants),u=[],p=(x,I,z)=>{const n=(x||[]).map(g=>String(g).toLowerCase());if(!n.length)return 1;const i=n.filter(g=>I.has(g));return i.length<n.length&&u.push(`missing ${z}: ${n.filter(g=>!I.has(g)).join(", ")}`),i.length/n.length},w=p(c.skills,k,"skills"),$=p(c.tools,M,"tools"),y=p(c.grants,b,"grants"),f=w*.5+$*.3+y*.2;f===1&&u.push("has every skill, tool and grant the job names");let S=.5,a=null;if(t&&t.entries){const x=t.jobsDone+t.jobsFailed?t.jobsDone/(t.jobsDone+t.jobsFailed):.5;a=h&&r&&t.rating.avg!=null?L(t,{qualityOf:r,costOf:d||void 0}):null;const I=t.rating.avg==null?.5:a?.adjusted??t.rating.avg;S=x*.5+I*.5,u.push(`${t.jobsDone} done, ${t.jobsFailed} failed${t.rating.avg!=null?a&&a.adjusted!==a.raw?`, rated ${Math.round(a.raw*100)}% raw, ${Math.round(a.adjusted*100)}% adjusted — ${a.basis[0]||"engine-corrected"}`:`, rated ${Math.round(t.rating.avg*100)}%`:""}`),a?.leverage!=null&&a.leverage>.05&&u.push(`adds ${a.leverage} over its engines' own quality`),a?.efficiency&&u.push(`cleared the bar cheapest on ${a.efficiency.engine}`),t.roles.orchestrator+t.roles.manager+t.roles["manager-of-managers"]>0&&u.push(`has led: ${t.roles.orchestrator} as orchestrator, ${t.roles.manager} as manager`)}else u.push("no record yet");const m=Number(e?.size?.steps)||0,o=m?Math.min(1,(t?.size?.largestSteps||0)/m)*.5+.5:1;return m&&(t?.size?.largestSteps||0)<m&&u.push(`largest task so far ${t?.size?.largestSteps||0} steps; this one is ~${m}`),{score:Math.round((f*.6+S*.3+o*.1)*1e3)/1e3,reasons:u,parts:{needs:f,record:S,size:o},...a?{adjusted:a}:{}}}function L(e,{qualityOf:s=()=>null,costOf:t=()=>null,reference:r=.6,k:d=.3,bar:h=.5}={}){const c=e?.rating?.avg??null,k=(e?.byEngine||[]).filter(o=>o.key).map(o=>({...o,quality:s(o.key)})).filter(o=>Number.isFinite(o.quality)),M=k.reduce((o,l)=>o+l.tasks,0),b=[];if(c==null||!k.length||!M)return{raw:c,adjusted:c,leverage:null,efficiency:null,basis:c==null?["not rated yet"]:["engines not rated yet — raw rating used"]};const u=d*k.reduce((o,l)=>o+l.tasks/M*(r-l.quality),0),p=Math.max(0,Math.min(1,c+u)),w=k.filter(o=>o.quality<r);w.length&&b.push(`${Math.round(w.reduce((o,l)=>o+l.tasks,0)/M*100)} % of its tasks ran on ${w.length===1?`a ${w[0].quality}-quality engine`:"engines below the reference"}`);const $=k.filter(o=>o.quality>r);$.length&&!w.length&&b.push(`ran on engines above the reference (${$.map(o=>o.quality).join(", ")})`);const y=k.filter(o=>o.rating?.avg!=null),f=y.reduce((o,l)=>o+l.rating.count,0),S=f?v(y.reduce((o,l)=>o+l.rating.count/f*(l.rating.avg-l.quality),0)):null;S!=null&&b.push(`${S>=0?"+":""}${S} over its engines' own quality`);const a=y.filter(o=>o.rating.avg>=h).map(o=>({...o,cost:t(o.key)??(o.tokens||null)})).filter(o=>o.cost!=null&&o.cost>0).sort((o,l)=>o.cost-l.cost),m=a.length?{value:v(p/a[0].cost),engine:a[0].key,costPerTask:a[0].cost}:null;return{raw:v(c),adjusted:v(p),leverage:S,efficiency:m,basis:b}}export{_ as ENGINE_KINDS,T as ROLE_KINDS,E as SCORECARD_ENTRY_KINDS,H as SCORECARD_VERSION,L as adjustSummary,U as attest,q as canonical,O as engineKey,W as fit,G as makeEntry,A as normalizeEngine,K as normalizeScm,D as sha256,V as summarize,P as verifyAttested,J as verifyChain};
|