@manyoars/oars 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -28
- package/dist/api.js +1 -1
- package/dist/bin.js +6 -29
- package/dist/cli.d.ts +6 -3
- package/dist/cli.js +367 -88
- package/dist/commands/keys.d.ts +31 -0
- package/dist/commands/keys.js +65 -0
- package/dist/commands/login.js +2 -2
- package/dist/errors.d.ts +13 -13
- package/dist/errors.js +28 -11
- package/dist/gate/checks.d.ts +68 -0
- package/dist/gate/checks.js +584 -0
- package/dist/gate/command.d.ts +14 -0
- package/dist/gate/command.js +61 -0
- package/dist/gate/hook-fill-main.d.ts +1 -0
- package/dist/gate/hook-fill-main.js +16 -0
- package/dist/gate/hook-fill.d.ts +38 -0
- package/dist/gate/hook-fill.js +94 -0
- package/dist/gate/hook-main.d.ts +1 -0
- package/dist/gate/hook-main.js +35 -0
- package/dist/gate/hook-process.d.ts +14 -0
- package/dist/gate/hook-process.js +64 -0
- package/dist/gate/hook.d.ts +46 -0
- package/dist/gate/hook.js +256 -0
- package/dist/gate/install.d.ts +25 -0
- package/dist/gate/install.js +126 -0
- package/dist/gate/local-bin.d.ts +8 -0
- package/dist/gate/local-bin.js +36 -0
- package/dist/gate/notes.d.ts +19 -0
- package/dist/gate/notes.js +27 -0
- package/dist/gate/novelty.d.ts +35 -0
- package/dist/gate/novelty.js +99 -0
- package/dist/gate/policy.d.ts +25 -0
- package/dist/gate/policy.js +73 -0
- package/dist/gate/record.d.ts +120 -0
- package/dist/gate/record.js +125 -0
- package/dist/gate/registry-state.d.ts +10 -0
- package/dist/gate/registry-state.js +30 -0
- package/dist/gate/registry.d.ts +56 -0
- package/dist/gate/registry.js +94 -0
- package/dist/gate/replay.d.ts +20 -0
- package/dist/gate/replay.js +257 -0
- package/dist/gate/report.d.ts +50 -0
- package/dist/gate/report.js +89 -0
- package/dist/gate/scope.d.ts +7 -0
- package/dist/gate/scope.js +27 -0
- package/dist/gate/transcripts.d.ts +68 -0
- package/dist/gate/transcripts.js +232 -0
- package/dist/hook-bin.d.ts +1 -0
- package/dist/hook-bin.js +25 -0
- package/dist/issuer.d.ts +7 -1
- package/dist/issuer.js +18 -1
- package/dist/main.d.ts +1 -0
- package/dist/main.js +64 -0
- package/dist/oidc.js +1 -1
- package/dist/sessions/actions.d.ts +35 -0
- package/dist/sessions/actions.js +351 -0
- package/dist/sessions/allow.d.ts +14 -0
- package/dist/sessions/allow.js +51 -0
- package/dist/sessions/archive.d.ts +11 -0
- package/dist/sessions/archive.js +38 -0
- package/dist/sessions/audit.d.ts +42 -0
- package/dist/sessions/audit.js +104 -0
- package/dist/sessions/baseline.d.ts +62 -0
- package/dist/sessions/baseline.js +188 -0
- package/dist/sessions/capture-runtime.d.ts +10 -0
- package/dist/sessions/capture-runtime.js +12 -0
- package/dist/sessions/cell-increments.d.ts +43 -0
- package/dist/sessions/cell-increments.js +209 -0
- package/dist/sessions/cells.d.ts +79 -0
- package/dist/sessions/cells.js +181 -0
- package/dist/sessions/codex.d.ts +53 -0
- package/dist/sessions/codex.js +679 -0
- package/dist/sessions/command.d.ts +117 -0
- package/dist/sessions/command.js +373 -0
- package/dist/sessions/copilot.d.ts +40 -0
- package/dist/sessions/copilot.js +432 -0
- package/dist/sessions/cursor.d.ts +24 -0
- package/dist/sessions/cursor.js +324 -0
- package/dist/sessions/derive.d.ts +57 -0
- package/dist/sessions/derive.js +320 -0
- package/dist/sessions/exclusion.d.ts +81 -0
- package/dist/sessions/exclusion.js +353 -0
- package/dist/sessions/extract.d.ts +179 -0
- package/dist/sessions/extract.js +1774 -0
- package/dist/sessions/fold.d.ts +17 -0
- package/dist/sessions/fold.js +71 -0
- package/dist/sessions/frame.d.ts +24 -0
- package/dist/sessions/frame.js +95 -0
- package/dist/sessions/gemini.d.ts +37 -0
- package/dist/sessions/gemini.js +613 -0
- package/dist/sessions/gold-arms.d.ts +67 -0
- package/dist/sessions/gold-arms.js +172 -0
- package/dist/sessions/gold.d.ts +197 -0
- package/dist/sessions/gold.js +463 -0
- package/dist/sessions/graph.d.ts +12 -0
- package/dist/sessions/graph.js +34 -0
- package/dist/sessions/grok.d.ts +36 -0
- package/dist/sessions/grok.js +421 -0
- package/dist/sessions/hook-calls.d.ts +3 -0
- package/dist/sessions/hook-calls.js +47 -0
- package/dist/sessions/hook-index.d.ts +53 -0
- package/dist/sessions/hook-index.js +157 -0
- package/dist/sessions/hooks.d.ts +13 -0
- package/dist/sessions/hooks.js +170 -0
- package/dist/sessions/identity.d.ts +22 -0
- package/dist/sessions/identity.js +101 -0
- package/dist/sessions/install.d.ts +54 -0
- package/dist/sessions/install.js +201 -0
- package/dist/sessions/lineage.d.ts +19 -0
- package/dist/sessions/lineage.js +118 -0
- package/dist/sessions/mcp.d.ts +6 -0
- package/dist/sessions/mcp.js +153 -0
- package/dist/sessions/merkle.d.ts +7 -0
- package/dist/sessions/merkle.js +28 -0
- package/dist/sessions/motif.d.ts +43 -0
- package/dist/sessions/motif.js +59 -0
- package/dist/sessions/package-resolve.d.ts +30 -0
- package/dist/sessions/package-resolve.js +183 -0
- package/dist/sessions/pending.d.ts +34 -0
- package/dist/sessions/pending.js +142 -0
- package/dist/sessions/pool-command.d.ts +44 -0
- package/dist/sessions/pool-command.js +542 -0
- package/dist/sessions/pool-delta.d.ts +54 -0
- package/dist/sessions/pool-delta.js +190 -0
- package/dist/sessions/pool-outbox.d.ts +63 -0
- package/dist/sessions/pool-outbox.js +260 -0
- package/dist/sessions/pool-print.d.ts +26 -0
- package/dist/sessions/pool-print.js +68 -0
- package/dist/sessions/pool-wire.d.ts +225 -0
- package/dist/sessions/pool-wire.js +620 -0
- package/dist/sessions/pool.d.ts +32 -0
- package/dist/sessions/pool.js +164 -0
- package/dist/sessions/projection.d.ts +75 -0
- package/dist/sessions/projection.js +165 -0
- package/dist/sessions/public-vocabulary.d.ts +73 -0
- package/dist/sessions/public-vocabulary.js +302 -0
- package/dist/sessions/query.d.ts +111 -0
- package/dist/sessions/query.js +246 -0
- package/dist/sessions/read-adoption.d.ts +2 -0
- package/dist/sessions/read-adoption.js +27 -0
- package/dist/sessions/read-call-shape.d.ts +2 -0
- package/dist/sessions/read-call-shape.js +25 -0
- package/dist/sessions/read-change.d.ts +2 -0
- package/dist/sessions/read-change.js +30 -0
- package/dist/sessions/read-cost.d.ts +4 -0
- package/dist/sessions/read-cost.js +37 -0
- package/dist/sessions/read-day-move.d.ts +3 -0
- package/dist/sessions/read-day-move.js +65 -0
- package/dist/sessions/read-fit.d.ts +7 -0
- package/dist/sessions/read-fit.js +41 -0
- package/dist/sessions/read-interaction.d.ts +5 -0
- package/dist/sessions/read-interaction.js +62 -0
- package/dist/sessions/read-lead-lag.d.ts +15 -0
- package/dist/sessions/read-lead-lag.js +81 -0
- package/dist/sessions/read-live.d.ts +6 -0
- package/dist/sessions/read-live.js +38 -0
- package/dist/sessions/read-method.d.ts +2 -0
- package/dist/sessions/read-method.js +29 -0
- package/dist/sessions/read-next-step.d.ts +2 -0
- package/dist/sessions/read-next-step.js +35 -0
- package/dist/sessions/read-order.d.ts +54 -0
- package/dist/sessions/read-order.js +62 -0
- package/dist/sessions/read-readable.d.ts +8 -0
- package/dist/sessions/read-readable.js +24 -0
- package/dist/sessions/read-recovery.d.ts +20 -0
- package/dist/sessions/read-recovery.js +97 -0
- package/dist/sessions/read-rows.d.ts +13 -0
- package/dist/sessions/read-rows.js +47 -0
- package/dist/sessions/read-substitution.d.ts +5 -0
- package/dist/sessions/read-substitution.js +51 -0
- package/dist/sessions/read-support.d.ts +48 -0
- package/dist/sessions/read-support.js +73 -0
- package/dist/sessions/read-tool-day.d.ts +2 -0
- package/dist/sessions/read-tool-day.js +26 -0
- package/dist/sessions/read-version-boundary.d.ts +31 -0
- package/dist/sessions/read-version-boundary.js +95 -0
- package/dist/sessions/read.d.ts +79 -0
- package/dist/sessions/read.js +84 -0
- package/dist/sessions/recall-consequence.d.ts +45 -0
- package/dist/sessions/recall-consequence.js +179 -0
- package/dist/sessions/recall-object.d.ts +137 -0
- package/dist/sessions/recall-object.js +77 -0
- package/dist/sessions/recall-pool.d.ts +19 -0
- package/dist/sessions/recall-pool.js +96 -0
- package/dist/sessions/recall-push.d.ts +26 -0
- package/dist/sessions/recall-push.js +95 -0
- package/dist/sessions/recall-reads.d.ts +117 -0
- package/dist/sessions/recall-reads.js +410 -0
- package/dist/sessions/recall-render.d.ts +19 -0
- package/dist/sessions/recall-render.js +168 -0
- package/dist/sessions/redact.d.ts +6 -0
- package/dist/sessions/redact.js +54 -0
- package/dist/sessions/repo-root.d.ts +10 -0
- package/dist/sessions/repo-root.js +101 -0
- package/dist/sessions/schema.d.ts +644 -0
- package/dist/sessions/schema.js +148 -0
- package/dist/sessions/session-shape.d.ts +73 -0
- package/dist/sessions/session-shape.js +342 -0
- package/dist/sessions/sources.d.ts +50 -0
- package/dist/sessions/sources.js +159 -0
- package/dist/sessions/steps.d.ts +105 -0
- package/dist/sessions/steps.js +250 -0
- package/dist/sessions/store.d.ts +297 -0
- package/dist/sessions/store.js +1478 -0
- package/dist/sessions/strata.d.ts +26 -0
- package/dist/sessions/strata.js +77 -0
- package/dist/sessions/survival.d.ts +25 -0
- package/dist/sessions/survival.js +230 -0
- package/dist/sessions/verify.d.ts +53 -0
- package/dist/sessions/verify.js +286 -0
- package/dist/sessions/vocab-load.d.ts +26 -0
- package/dist/sessions/vocab-load.js +61 -0
- package/dist/sessions/vscode-copilot.d.ts +38 -0
- package/dist/sessions/vscode-copilot.js +477 -0
- package/package.json +4 -5
- package/vocab/crates.json +1 -0
- package/vocab/go.json +1 -0
- package/vocab/harness.json +1 -0
- package/vocab/homebrew.json +1 -0
- package/vocab/manifest.json +1 -0
- package/vocab/maven.json +1 -0
- package/vocab/models.json +1 -0
- package/vocab/npm.json +1 -0
- package/vocab/programs.json +1 -0
- package/vocab/pypi.json +1 -0
- package/vocab/rubygems.json +1 -0
- package/dist/commands/deploy-input.d.ts +0 -38
- package/dist/commands/deploy-input.js +0 -150
- package/dist/commands/deploy.d.ts +0 -22
- package/dist/commands/deploy.js +0 -124
- package/dist/commands/status.d.ts +0 -7
- package/dist/commands/status.js +0 -18
- package/dist/commands/templates.d.ts +0 -11
- package/dist/commands/templates.js +0 -32
- package/dist/commands/test-charge.d.ts +0 -11
- package/dist/commands/test-charge.js +0 -34
- package/dist/journal.d.ts +0 -26
- package/dist/journal.js +0 -58
- package/parity/deploy-plans/paid-api.json +0 -34
- package/parity/deploy-plans/per-task-agent.json +0 -34
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// What an agent reads from a recall. Answers render from structured fields only (spec Section 3): every name passes a closed test and the rest is numbers, so
|
|
2
|
+
// error text and other authored strings stay out of an agent's context, whatever a store's records hold.
|
|
3
|
+
import { ACTION_TABLES, actionOf, CANONICAL_ACTIONS } from './actions.js';
|
|
4
|
+
import { LITERAL, PUBLIC_MCP_SERVERS, PUBLIC_PACKAGES, publicModel, publicVersion } from './public-vocabulary.js';
|
|
5
|
+
import { PROMPT_CLASSES } from './schema.js';
|
|
6
|
+
const DAY_MS = 86_400_000;
|
|
7
|
+
const KEY_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,25}$/;
|
|
8
|
+
const KEY_WORD_BREAK = /_+|(?<=[a-z0-9])(?=[A-Z])/;
|
|
9
|
+
const MAX_KEY_WORDS = 3;
|
|
10
|
+
const DAY = /^\d{4}-\d{2}-\d{2}$/;
|
|
11
|
+
const READ_ID = /^(read:)?[0-9a-f]{16}$/;
|
|
12
|
+
/**
|
|
13
|
+
* An input key is an identifier of at most three words: in a scan of real tool rows, 390 of 406 distinct keys were, `run_in_background` and `dangerouslyDisableSandbox` among them.
|
|
14
|
+
* Any longer and an identifier can say a whole instruction. A rare longer key is only left off the line; the shape's ID still holds it.
|
|
15
|
+
*/
|
|
16
|
+
const isKeyName = (key) => KEY_NAME.test(key) && key.split(KEY_WORD_BREAK).filter((word) => word !== '').length <= MAX_KEY_WORDS;
|
|
17
|
+
/** A public name as it stands: a package, a model, a canonical action, a tool a harness's table lists, or a published MCP server. Anything else is what the asker or a record wrote. */
|
|
18
|
+
export const closedName = (name) => {
|
|
19
|
+
const isPublic = PUBLIC_PACKAGES.has(name) || publicModel(name) === name || CANONICAL_ACTIONS.some((action) => action === name) || Object.values(ACTION_TABLES).some((table) => Object.hasOwn(table.rows, name)) || PUBLIC_MCP_SERVERS.has(name);
|
|
20
|
+
return isPublic ? name : LITERAL;
|
|
21
|
+
};
|
|
22
|
+
const known = (value) => value !== null && value !== undefined && value !== LITERAL;
|
|
23
|
+
const harnessName = (name) => (Object.hasOwn(ACTION_TABLES, name) ? name : null);
|
|
24
|
+
const publicDay = (day) => (day !== null && DAY.test(day) ? day : null);
|
|
25
|
+
const versionParts = (version) => {
|
|
26
|
+
const parts = version.split('.').map(Number);
|
|
27
|
+
return parts.every((part) => Number.isInteger(part)) ? parts : undefined;
|
|
28
|
+
};
|
|
29
|
+
/** The gap in the last component when every other component agrees; undefined when the two do not line up as one count, which an agent must not read as near. */
|
|
30
|
+
const lastGap = (asker, evidence) => {
|
|
31
|
+
const mine = versionParts(asker);
|
|
32
|
+
const theirs = versionParts(evidence);
|
|
33
|
+
if (mine === undefined || theirs === undefined || mine.length !== theirs.length)
|
|
34
|
+
return undefined;
|
|
35
|
+
const last = mine.length - 1;
|
|
36
|
+
if (mine.slice(0, last).some((part, index) => part !== theirs[index]))
|
|
37
|
+
return undefined;
|
|
38
|
+
return (mine[last] ?? 0) - (theirs[last] ?? 0);
|
|
39
|
+
};
|
|
40
|
+
const newest = (versions) => versions.flatMap((version) => (versionParts(version) === undefined ? [] : [version])).sort((a, b) => a.localeCompare(b, 'en', { numeric: true })).pop();
|
|
41
|
+
const axis = (asker, evidence) => {
|
|
42
|
+
if (!known(asker) || evidence.length === 0)
|
|
43
|
+
return 'unknown';
|
|
44
|
+
return evidence.includes(asker) ? 'same' : 'differs';
|
|
45
|
+
};
|
|
46
|
+
export const deltaOf = (asker, read) => {
|
|
47
|
+
const version = asker?.harnessVersion;
|
|
48
|
+
const latest = newest(read.harnessVersions);
|
|
49
|
+
const model = axis(asker?.model, read.models);
|
|
50
|
+
const lastDay = publicDay(read.lastDay);
|
|
51
|
+
const today = publicDay(asker?.day ?? null);
|
|
52
|
+
return {
|
|
53
|
+
harness: axis(asker?.harness, read.harnesses),
|
|
54
|
+
harnessVersions: known(version) && latest !== undefined ? (lastGap(version, latest) ?? null) : null,
|
|
55
|
+
model,
|
|
56
|
+
modelVersions: model === 'unknown' ? null : read.models.filter((name) => name !== asker?.model).length,
|
|
57
|
+
// A cell's frame bucket holds no platform, shell, or stack, so a read cannot state them: unknown, never a guess of `same`.
|
|
58
|
+
platform: 'unknown',
|
|
59
|
+
shell: 'unknown',
|
|
60
|
+
stack: 'unknown',
|
|
61
|
+
toolVersion: null,
|
|
62
|
+
ageDays: lastDay === null || today === null ? null : Math.round((Date.parse(today) - Date.parse(lastDay)) / DAY_MS),
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
const closedNames = (values, isPublic) => values.flatMap((value) => {
|
|
66
|
+
const name = isPublic(value);
|
|
67
|
+
return known(name) ? [name] : [];
|
|
68
|
+
});
|
|
69
|
+
/** A class P row an agent also reads over MCP, so its names pass the same closed tests a rendered answer does, and a value they refuse is dropped. */
|
|
70
|
+
export const evidenceOf = (read, asker) => ({
|
|
71
|
+
ref: READ_ID.test(read.readId) ? read.readId : LITERAL,
|
|
72
|
+
frameRange: { harnesses: closedNames(read.harnesses, harnessName), harnessVersions: closedNames(read.harnessVersions, publicVersion), models: closedNames(read.models, publicModel) },
|
|
73
|
+
firstDay: publicDay(read.firstDay),
|
|
74
|
+
lastDay: publicDay(read.lastDay),
|
|
75
|
+
n: read.n,
|
|
76
|
+
clusters: read.clusters,
|
|
77
|
+
delta: deltaOf(asker, read),
|
|
78
|
+
});
|
|
79
|
+
export const count = (value, noun) => `${String(value)} ${noun}${value === 1 ? '' : 's'}`;
|
|
80
|
+
/** A range of public versions, newest last; a version the vocabulary refuses is dropped, never shown. */
|
|
81
|
+
const versionRange = (versions) => {
|
|
82
|
+
const shown = closedNames(versions, publicVersion);
|
|
83
|
+
if (shown.length === 0)
|
|
84
|
+
return 'version unknown';
|
|
85
|
+
const sorted = [...shown].sort((a, b) => a.localeCompare(b, 'en', { numeric: true }));
|
|
86
|
+
const [first] = sorted;
|
|
87
|
+
const last = sorted[sorted.length - 1];
|
|
88
|
+
return first === last ? String(first) : `${String(first)}-${String(last)}`;
|
|
89
|
+
};
|
|
90
|
+
const modelsOf = (models) => {
|
|
91
|
+
const shown = closedNames(models, publicModel);
|
|
92
|
+
return shown.length === 0 ? 'model unknown' : shown.join(', ');
|
|
93
|
+
};
|
|
94
|
+
const signed = (value) => (value > 0 ? `+${String(value)}` : String(value));
|
|
95
|
+
const distance = (delta) => {
|
|
96
|
+
const harness = delta.harness !== 'same' ? delta.harness : delta.harnessVersions === null ? 'same, version gap unknown' : delta.harnessVersions === 0 ? 'same' : signed(delta.harnessVersions);
|
|
97
|
+
const age = delta.ageDays === null ? 'unknown' : count(delta.ageDays, 'day');
|
|
98
|
+
return `from you: harness ${harness}, model ${delta.model}, age ${age}`;
|
|
99
|
+
};
|
|
100
|
+
const isCanonical = (name) => CANONICAL_ACTIONS.some((action) => action === name);
|
|
101
|
+
/** What a native tool is to the harnesses a read ran under: the first table that lists it, else what any of them can say of it. A read naming no harness asks every table. */
|
|
102
|
+
const toolOf = (read, tool) => {
|
|
103
|
+
const tables = Object.entries(ACTION_TABLES).filter(([name]) => read.harnesses.includes(name));
|
|
104
|
+
const said = (tables.length === 0 ? Object.values(ACTION_TABLES) : tables.map(([, table]) => table)).map((table) => actionOf(table, tool));
|
|
105
|
+
return said.find((entry) => entry.native !== LITERAL) ?? said.find((entry) => entry.action !== 'other') ?? { action: 'other', native: LITERAL };
|
|
106
|
+
};
|
|
107
|
+
/** A stored motif read's name: its class, or its canonical steps without their identities. Undefined for a name that is neither, such as a native tool. */
|
|
108
|
+
const motifOf = (tool) => {
|
|
109
|
+
if (PROMPT_CLASSES.some((cls) => cls === tool))
|
|
110
|
+
return tool;
|
|
111
|
+
const steps = tool.split('>').map((step) => step.replace(/\[.*$/, ''));
|
|
112
|
+
return steps.every(isCanonical) ? steps.join('>') : undefined;
|
|
113
|
+
};
|
|
114
|
+
const nameOf = (read) => {
|
|
115
|
+
// An entity read is about the name itself, which need not be a tool: `actionOf` would read a model or a server as `other`, naming nothing.
|
|
116
|
+
if (read.family === 'entity')
|
|
117
|
+
return closedName(read.tool);
|
|
118
|
+
// A stored read whose slice names no motif or action goes by its family, which is a closed name too.
|
|
119
|
+
if (read.tool === read.family)
|
|
120
|
+
return read.family;
|
|
121
|
+
const motif = motifOf(read.tool);
|
|
122
|
+
if (motif !== undefined)
|
|
123
|
+
return motif;
|
|
124
|
+
const { action, native } = toolOf(read, read.tool);
|
|
125
|
+
const name = native === LITERAL ? action : native;
|
|
126
|
+
const keys = read.keys.filter(isKeyName);
|
|
127
|
+
return keys.length === 0 ? name : `${name} [${keys.join(', ')}]`;
|
|
128
|
+
};
|
|
129
|
+
/** Several tools can sit behind one action, and the same word twice would read as two things, so each action is stated once with the sum. A next-step read already states actions. */
|
|
130
|
+
const followedActions = (read) => {
|
|
131
|
+
const sums = new Map();
|
|
132
|
+
for (const [tool, times] of read.followed) {
|
|
133
|
+
const action = isCanonical(tool) ? tool : toolOf(read, tool).action;
|
|
134
|
+
sums.set(action, (sums.get(action) ?? 0) + times);
|
|
135
|
+
}
|
|
136
|
+
return [...sums].sort((a, b) => b[1] - a[1]);
|
|
137
|
+
};
|
|
138
|
+
/** What a read with no denominator counts. A model is not called: its entity read counts the transcript lines that requested it. */
|
|
139
|
+
const nounOf = (read) => {
|
|
140
|
+
if (read.family === 'entity')
|
|
141
|
+
return read.models.includes(read.tool) ? 'request' : 'call';
|
|
142
|
+
if (read.family === 'next-step')
|
|
143
|
+
return 'call';
|
|
144
|
+
return read.family === 'recovery' || read.family === 'fit' ? 'error' : 'observation';
|
|
145
|
+
};
|
|
146
|
+
const line = (read, asker) => {
|
|
147
|
+
const followed = followedActions(read);
|
|
148
|
+
const failures = read.calls === null ? count(read.count, nounOf(read)) : `failed ${String(read.count)} of ${count(read.calls, 'call')}`;
|
|
149
|
+
const harnesses = read.harnesses.flatMap((name) => harnessName(name) ?? []);
|
|
150
|
+
const lastDay = publicDay(read.lastDay) ?? 'unknown';
|
|
151
|
+
const id = READ_ID.test(read.readId) ? read.readId : LITERAL;
|
|
152
|
+
const parts = [
|
|
153
|
+
`${nameOf(read)} | ${failures}, ${count(read.n, 'session')}, ${count(read.clusters, 'cluster')} | ${harnesses.join(', ') || 'harness unknown'} ${versionRange(read.harnessVersions)}, ${modelsOf(read.models)}, last ${lastDay}`,
|
|
154
|
+
` ${distance(deltaOf(asker, read))}`,
|
|
155
|
+
...(followed.length === 0 ? [] : [` then: ${followed.map(([action, times]) => `${action} x${String(times)}`).join(', ')}`]),
|
|
156
|
+
` read ${id} (${read.family})`,
|
|
157
|
+
];
|
|
158
|
+
return { text: parts.join('\n'), names: followed.map(([action]) => action) };
|
|
159
|
+
};
|
|
160
|
+
const PREAMBLE = 'oars recall, counts from earlier sessions:';
|
|
161
|
+
export const renderAnswer = (reads, asker) => {
|
|
162
|
+
if (reads.length === 0)
|
|
163
|
+
return { rendered: '', names: [] };
|
|
164
|
+
const lines = reads.map((read) => line(read, asker));
|
|
165
|
+
return { rendered: [PREAMBLE, ...lines.map((entry) => entry.text)].join('\n'), names: [...new Set(lines.flatMap((entry) => entry.names))] };
|
|
166
|
+
};
|
|
167
|
+
/** The text for a miss: what was asked for is left out, because the asker wrote it. */
|
|
168
|
+
export const MISS_TEXT = 'oars recall: no answer in the local store.';
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const PATTERNS: ReadonlyArray<readonly [RegExp, string]>;
|
|
2
|
+
/** Shannon entropy in bits per character. */
|
|
3
|
+
export declare const entropy: (text: string) => number;
|
|
4
|
+
export type Redactor = (text: string) => string;
|
|
5
|
+
/** `domains` are the organization domains to mask (`example.com` also masks `mail.example.com`). */
|
|
6
|
+
export declare const makeRedactor: (domains?: readonly string[]) => Redactor;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// Redaction before anything is written to the session store. Deterministic and conservative: it would rather over-mask a
|
|
2
|
+
// long random-looking token than let a secret through. Pattern rules first, then emails, the organization domains the
|
|
3
|
+
// operator names, and last an entropy rule for long mixed-case tokens no pattern knew.
|
|
4
|
+
export const PATTERNS = [
|
|
5
|
+
[/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g, '<secret>'],
|
|
6
|
+
[/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g, '<secret>'],
|
|
7
|
+
[/\bgh[pousr]_[A-Za-z0-9]{20,}\b/g, '<secret>'],
|
|
8
|
+
[/\bgithub_pat_[A-Za-z0-9_]{20,}\b/g, '<secret>'],
|
|
9
|
+
[/\bsk-(?:ant-)?[A-Za-z0-9_-]{16,}\b/g, '<secret>'],
|
|
10
|
+
[/\b(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{10,}\b/g, '<secret>'],
|
|
11
|
+
[/\bwhsec_[A-Za-z0-9]{10,}\b/g, '<secret>'],
|
|
12
|
+
[/\bxox[abprs]-[A-Za-z0-9-]{10,}\b/g, '<secret>'],
|
|
13
|
+
[/\bAIza[0-9A-Za-z_-]{30,}\b/g, '<secret>'],
|
|
14
|
+
[/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g, '<secret>'],
|
|
15
|
+
[/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]{12,}/gi, '$1 <secret>'],
|
|
16
|
+
// user:password@host inside a URL
|
|
17
|
+
[/(\b[a-z][a-z0-9+.-]*:\/\/)[^\s/:@]+:[^\s/@]+@/gi, '$1<secret>@'],
|
|
18
|
+
// NAME=value and "name": "value" where the name says it is a secret
|
|
19
|
+
[/\b([A-Za-z0-9_-]*(?:password|passwd|secret|token|api[_-]?key|private[_-]?key|credential)[A-Za-z0-9_-]*)(["']?\s*[=:]\s*)(["']?)[^\s"'&,;]{4,}\3/gi, '$1$2$3<secret>$3'],
|
|
20
|
+
];
|
|
21
|
+
const EMAIL = /[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(?:\.[A-Za-z0-9-]+)+/g;
|
|
22
|
+
// A pinned alias, `foo@npm:bar@1.0.0` or `jsr:@scope/bar@1.0.0`, ends in `bar@1.0.0`, which EMAIL reads as an address. Digits and dots after
|
|
23
|
+
// the `@` are a version, never a domain, so that one shape right after `npm:` or `jsr:` (and an optional scope) is kept. An all-numeric four-part tail is an
|
|
24
|
+
// IP address, a real host, so it stays masked (as in `extract.ts` `VERSIONED`); any other address stays masked too.
|
|
25
|
+
const ALIAS_PREFIX = /(?<![A-Za-z0-9])(?:npm|jsr):(?:@[A-Za-z0-9._~-]+\/)?$/;
|
|
26
|
+
const DOTTED_VERSION = /@(?!\d+\.\d+\.\d+\.\d+$)\d+(?:\.\d+)+$/;
|
|
27
|
+
const TOKEN = /[A-Za-z0-9+_=-]{24,}/g;
|
|
28
|
+
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
29
|
+
/** Shannon entropy in bits per character. */
|
|
30
|
+
export const entropy = (text) => {
|
|
31
|
+
const counts = new Map();
|
|
32
|
+
for (const char of text)
|
|
33
|
+
counts.set(char, (counts.get(char) ?? 0) + 1);
|
|
34
|
+
let bits = 0;
|
|
35
|
+
for (const count of counts.values()) {
|
|
36
|
+
const p = count / text.length;
|
|
37
|
+
bits -= p * Math.log2(p);
|
|
38
|
+
}
|
|
39
|
+
return bits;
|
|
40
|
+
};
|
|
41
|
+
const looksRandom = (token) => /[a-z]/.test(token) && /[A-Z]/.test(token) && /\d/.test(token) && entropy(token) >= 4;
|
|
42
|
+
/** `domains` are the organization domains to mask (`example.com` also masks `mail.example.com`). */
|
|
43
|
+
export const makeRedactor = (domains = []) => {
|
|
44
|
+
const domainRule = domains.length === 0 ? undefined : new RegExp(`(?:[A-Za-z0-9-]+\\.)*(?:${domains.map((domain) => escapeRegExp(domain.toLowerCase())).join('|')})\\b`, 'gi');
|
|
45
|
+
return (text) => {
|
|
46
|
+
let out = text;
|
|
47
|
+
for (const [pattern, replacement] of PATTERNS)
|
|
48
|
+
out = out.replace(pattern, replacement);
|
|
49
|
+
out = out.replace(EMAIL, (match, offset, whole) => DOTTED_VERSION.test(match) && ALIAS_PREFIX.test(whole.slice(0, offset)) ? match : '<email>');
|
|
50
|
+
if (domainRule !== undefined)
|
|
51
|
+
out = out.replace(domainRule, '<org-domain>');
|
|
52
|
+
return out.replace(TOKEN, (token) => (looksRandom(token) ? '<secret>' : token));
|
|
53
|
+
};
|
|
54
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** A resolver for working directories; answers are cached, since a transcript repeats a handful of directories thousands of times. */
|
|
2
|
+
export declare const makeRepoRoot: (exists?: (path: string) => boolean) => ((cwd: string) => string | null);
|
|
3
|
+
/** `https://User:pw@GitHub.com/org/repo.git`, `git@github.com:org/repo.git`, and `ssh://git@github.com/org/repo/` all read `github.com/org/repo`. */
|
|
4
|
+
export declare const normalizeRemote: (url: string) => string;
|
|
5
|
+
/**
|
|
6
|
+
* A resolver from a repository root to its identity string: `origin:<normalized remote>` when the repository's `config` names an
|
|
7
|
+
* `origin`, else `path:<root>`. The `config` is `<root>/.git/config`, or for a linked worktree (`.git` is a file) the common
|
|
8
|
+
* directory's, reached through the file's `gitdir:` line and that directory's `commondir` file. Any step failing falls back to the path.
|
|
9
|
+
*/
|
|
10
|
+
export declare const makeRepoIdentity: (read?: (path: string) => string | null) => ((root: string) => string);
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// The repository root a working directory sits in, for hashing file paths from the repository rather than from wherever the harness ran
|
|
2
|
+
// (spec Section 1.1, CAPTURE 4). A linked worktree under `<root>/.claude/worktrees/<name>` resolves to `<root>`, as A9 counted it; any
|
|
3
|
+
// other directory resolves to the nearest ancestor holding a `.git` entry (a folder, or the file a linked worktree keeps). The walk
|
|
4
|
+
// looks at the file system rather than running `git rev-parse`, so it costs no process per distinct directory and still answers for a
|
|
5
|
+
// directory that has since been deleted (it walks up to the nearest ancestor that exists). Absent a repository it answers null.
|
|
6
|
+
//
|
|
7
|
+
// `makeRepoIdentity` names the repository itself, so two clones of one repository, or one clone on two machines, share a `repo` value:
|
|
8
|
+
// the normalized `origin` remote URL read from the repository's `config` (no git process), else the absolute root path.
|
|
9
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
10
|
+
import { posix } from 'node:path';
|
|
11
|
+
const WORKTREE = /^(.+?)\/\.claude\/worktrees\/[^/]+(?:\/.*)?$/;
|
|
12
|
+
/** A resolver for working directories; answers are cached, since a transcript repeats a handful of directories thousands of times. */
|
|
13
|
+
export const makeRepoRoot = (exists = existsSync) => {
|
|
14
|
+
const cache = new Map();
|
|
15
|
+
return (cwd) => {
|
|
16
|
+
const start = cwd.replace(/\\/g, '/').replace(/\/+$/, '');
|
|
17
|
+
const known = cache.get(start);
|
|
18
|
+
if (known !== undefined)
|
|
19
|
+
return known;
|
|
20
|
+
const linked = WORKTREE.exec(start);
|
|
21
|
+
let found = linked?.[1] ?? null;
|
|
22
|
+
// posix.dirname('C:') is '.', which would probe the deposit process's own directory: stop at a drive or filesystem root first.
|
|
23
|
+
for (let dir = start; found === null;) {
|
|
24
|
+
if (exists(`${dir}/.git`))
|
|
25
|
+
found = dir;
|
|
26
|
+
const parent = posix.dirname(dir);
|
|
27
|
+
if (found !== null || parent === '.' || parent === dir)
|
|
28
|
+
break;
|
|
29
|
+
dir = parent;
|
|
30
|
+
}
|
|
31
|
+
cache.set(start, found);
|
|
32
|
+
return found;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
/** `https://User:pw@GitHub.com/org/repo.git`, `git@github.com:org/repo.git`, and `ssh://git@github.com/org/repo/` all read `github.com/org/repo`. */
|
|
36
|
+
export const normalizeRemote = (url) => {
|
|
37
|
+
const bare = url
|
|
38
|
+
.trim()
|
|
39
|
+
.replace(/^[a-z][a-z0-9+.-]*:\/\//i, '')
|
|
40
|
+
.replace(/^[^@/]*@/, '')
|
|
41
|
+
.replace(/^([^/:]+):(?!\d+(?:\/|$))/, '$1/')
|
|
42
|
+
.replace(/\/+$/, '')
|
|
43
|
+
.replace(/\.git$/, '')
|
|
44
|
+
.replace(/\/+$/, '');
|
|
45
|
+
const slash = bare.indexOf('/');
|
|
46
|
+
return slash < 0 ? bare.toLowerCase() : `${bare.slice(0, slash).toLowerCase()}${bare.slice(slash)}`;
|
|
47
|
+
};
|
|
48
|
+
/** The `url` of `[remote "origin"]` in a git `config`, or null. */
|
|
49
|
+
const originUrl = (config) => {
|
|
50
|
+
let inOrigin = false;
|
|
51
|
+
for (const line of config.split(/\r?\n/)) {
|
|
52
|
+
const section = /^\s*\[(.*)\]\s*$/.exec(line);
|
|
53
|
+
if (section !== null)
|
|
54
|
+
inOrigin = /^remote\s+"origin"$/i.test(section[1] ?? '');
|
|
55
|
+
else if (inOrigin) {
|
|
56
|
+
const url = /^\s*url\s*=\s*(.+?)\s*$/i.exec(line);
|
|
57
|
+
if (url !== null)
|
|
58
|
+
return url[1] ?? null;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
};
|
|
63
|
+
const isAbsolutePath = (path) => path.startsWith('/') || /^[A-Za-z]:\//.test(path);
|
|
64
|
+
const resolveFrom = (base, path) => (isAbsolutePath(path) ? path : posix.normalize(`${base}/${path}`));
|
|
65
|
+
/** The text of a file, or null when it cannot be read. */
|
|
66
|
+
const readOrNull = (path) => {
|
|
67
|
+
try {
|
|
68
|
+
return readFileSync(path, 'utf8');
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* A resolver from a repository root to its identity string: `origin:<normalized remote>` when the repository's `config` names an
|
|
76
|
+
* `origin`, else `path:<root>`. The `config` is `<root>/.git/config`, or for a linked worktree (`.git` is a file) the common
|
|
77
|
+
* directory's, reached through the file's `gitdir:` line and that directory's `commondir` file. Any step failing falls back to the path.
|
|
78
|
+
*/
|
|
79
|
+
export const makeRepoIdentity = (read = readOrNull) => {
|
|
80
|
+
const cache = new Map();
|
|
81
|
+
const configOf = (root) => {
|
|
82
|
+
const direct = read(`${root}/.git/config`);
|
|
83
|
+
if (direct !== null)
|
|
84
|
+
return direct;
|
|
85
|
+
const pointer = /^gitdir:\s*(.+?)\s*$/m.exec(read(`${root}/.git`) ?? '')?.[1];
|
|
86
|
+
if (pointer === undefined)
|
|
87
|
+
return null;
|
|
88
|
+
const gitdir = resolveFrom(root, pointer);
|
|
89
|
+
const common = read(`${gitdir}/commondir`)?.trim();
|
|
90
|
+
return read(`${common === undefined || common === '' ? gitdir : resolveFrom(gitdir, common)}/config`);
|
|
91
|
+
};
|
|
92
|
+
return (root) => {
|
|
93
|
+
const known = cache.get(root);
|
|
94
|
+
if (known !== undefined)
|
|
95
|
+
return known;
|
|
96
|
+
const url = originUrl(configOf(root) ?? '');
|
|
97
|
+
const identity = url === null ? `path:${root.toLowerCase()}` : `origin:${normalizeRemote(url)}`;
|
|
98
|
+
cache.set(root, identity);
|
|
99
|
+
return identity;
|
|
100
|
+
};
|
|
101
|
+
};
|