@foldspace_npm/harness 0.1.33 → 0.1.34
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/CLAUDE.md +169 -49
- package/README.md +30 -2
- package/bin/inject.mjs +2 -18
- package/bin/observe.mjs +18 -0
- package/bin/record.mjs +2 -0
- package/bin/sync.mjs +48 -0
- package/package.json +1 -1
- package/recipes/INDEX.md +1 -1
- package/recipes/bottom-bar/README.md +24 -11
- package/recipes/bottom-bar/agent/bottomBar.ts +76 -38
- package/recipes/bottom-bar/fixtures/configuration.sent.json +13 -12
- package/recipes/swap-the-login-method/README.md +1 -1
- package/recipes/swap-the-login-method/agent/utils.ts +8 -7
- package/recipes/swap-the-login-method/variants/utils.cookies.ts +8 -7
- package/src/chrome-binary.mjs +33 -0
- package/src/cli-registry.mjs +52 -5
- package/src/customer-subdomain.mjs +149 -0
- package/src/lint/index.mjs +23 -3
- package/src/lint/rules.mjs +46 -2
- package/src/lint/safety.mjs +785 -0
- package/src/observe-core.mjs +1 -1
- package/src/runtime/VERSION +1 -1
- package/src/runtime/config.ts +15 -2
- package/src/runtime/http.ts +40 -3
- package/src/runtime/index.ts +1 -0
- package/src/sync-core.mjs +374 -0
- package/src/upgrade.mjs +36 -7
- package/src/video/compose.mjs +12 -3
- package/src/video/recorder.mjs +15 -7
- package/templates/agent-starter/agent/constants.ts +15 -1
- package/templates/agent-starter/agent/utils.ts +3 -0
- package/templates/agent-starter/claude-settings.json +3 -1
- package/templates/agent-starter/skills/review-experience/SKILL.md +135 -0
- package/templates/agent-starter/skills/share-a-video/SKILL.md +9 -2
package/src/lint/index.mjs
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { listSourceFiles, parseFile, shouldSkipActionFile } from "./ast.mjs";
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
lintActionFile,
|
|
6
|
+
lintViewsFile,
|
|
7
|
+
ruleNoTestModeInBundle,
|
|
8
|
+
ruleRegistryIntegrity,
|
|
9
|
+
ruleStartersLimit,
|
|
10
|
+
} from "./rules.mjs";
|
|
11
|
+
import { lintSafetyFile, readProjectFacts, ruleNoSecrets } from "./safety.mjs";
|
|
5
12
|
|
|
6
13
|
export { bundleTurnsOnTestMode } from "./rules.mjs";
|
|
7
14
|
|
|
@@ -47,12 +54,25 @@ export function lintProject(projectDir) {
|
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
// Any file under agent/ can end up in the bundle, including code that runs
|
|
50
|
-
// as soon as the bundle loads, so
|
|
57
|
+
// as soon as the bundle loads, so these rules read all of it - not only
|
|
51
58
|
// actions and views.
|
|
52
59
|
for (const filePath of listSourceFiles(path.join(projectDir, "agent"))) {
|
|
53
|
-
|
|
60
|
+
const sourceFile = parseFile(filePath);
|
|
61
|
+
const relative = path.relative(projectDir, filePath);
|
|
62
|
+
ruleNoTestModeInBundle(sourceFile, relative, findings);
|
|
63
|
+
ruleStartersLimit(sourceFile, relative, findings);
|
|
54
64
|
}
|
|
55
65
|
|
|
66
|
+
// Safety: every file under agent/ the project owns - not agent/runtime/,
|
|
67
|
+
// which is the harness's own copy - and, for secrets, the whole project.
|
|
68
|
+
const facts = readProjectFacts(projectDir);
|
|
69
|
+
for (const filePath of listSourceFiles(path.join(projectDir, "agent"))) {
|
|
70
|
+
const relative = path.relative(projectDir, filePath);
|
|
71
|
+
if (relative.split(path.sep)[1] === "runtime") continue;
|
|
72
|
+
lintSafetyFile(filePath, relative, findings, facts);
|
|
73
|
+
}
|
|
74
|
+
ruleNoSecrets(projectDir, findings);
|
|
75
|
+
|
|
56
76
|
ruleRegistryIntegrity({
|
|
57
77
|
indexFile: entry,
|
|
58
78
|
indexSource: parseFile(entry),
|
package/src/lint/rules.mjs
CHANGED
|
@@ -22,9 +22,11 @@ import {
|
|
|
22
22
|
import { ts } from "./ts.mjs";
|
|
23
23
|
|
|
24
24
|
/*
|
|
25
|
+
Safety rules (one customer's address, the user's token, secrets, known
|
|
26
|
+
hosts) live in ./safety.mjs.
|
|
27
|
+
|
|
25
28
|
Backlog, not this slice:
|
|
26
|
-
- no-
|
|
27
|
-
- no-unsafe-return (stack / cookies / Authorization / ApiResult.detail)
|
|
29
|
+
- no-unsafe-return (stack / ApiResult.detail)
|
|
28
30
|
- snake_case action keys
|
|
29
31
|
- credentials: "include" on every fetch
|
|
30
32
|
- _gen2 vs _v2 taskKey
|
|
@@ -804,6 +806,48 @@ export function ruleNoTestModeInBundle(sourceFile, relative, findings) {
|
|
|
804
806
|
walk(sourceFile, undefined);
|
|
805
807
|
}
|
|
806
808
|
|
|
809
|
+
// The most starters one page (or the agent-wide default) shows, both groups
|
|
810
|
+
// together. `setConversationStarters` also sets the panel's opening screen,
|
|
811
|
+
// and a long list there buries the one that matters (a customer default had 7).
|
|
812
|
+
export const MAX_STARTERS = 3;
|
|
813
|
+
|
|
814
|
+
// Any object with a KNOWLEDGE or ACTION list - a setConversationStarters
|
|
815
|
+
// call, a page's entry in the bottom bar's list, conversationStartersSettings
|
|
816
|
+
// - is counted. Only lists written out in the file are counted: a list built
|
|
817
|
+
// at run time (`.map`, a spread) is left alone.
|
|
818
|
+
export function ruleStartersLimit(sourceFile, relative, findings) {
|
|
819
|
+
const bindings = collectModuleBindings(sourceFile);
|
|
820
|
+
const visit = (node) => {
|
|
821
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
822
|
+
let count = 0;
|
|
823
|
+
let counted = false;
|
|
824
|
+
for (const prop of objectProperties(node)) {
|
|
825
|
+
const name = propertyName(prop);
|
|
826
|
+
if (name !== "KNOWLEDGE" && name !== "ACTION") continue;
|
|
827
|
+
const list = resolveNode(propertyValue(prop), bindings);
|
|
828
|
+
if (!list || !ts.isArrayLiteralExpression(list)) continue;
|
|
829
|
+
counted = true;
|
|
830
|
+
count += list.elements.filter((element) => !ts.isSpreadElement(element)).length;
|
|
831
|
+
}
|
|
832
|
+
if (counted && count > MAX_STARTERS) {
|
|
833
|
+
findings.push(
|
|
834
|
+
finding({
|
|
835
|
+
rule: "starters-limit",
|
|
836
|
+
severity: "error",
|
|
837
|
+
sourceFile,
|
|
838
|
+
node,
|
|
839
|
+
file: relative,
|
|
840
|
+
message: `${count} conversation starters in one set; at most ${MAX_STARTERS}, KNOWLEDGE and ACTION together. The same list is the agent panel's opening screen.`,
|
|
841
|
+
fix: `Keep the ${MAX_STARTERS} that matter most on this page - each one something the agent can answer today.`,
|
|
842
|
+
}),
|
|
843
|
+
);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
ts.forEachChild(node, visit);
|
|
847
|
+
};
|
|
848
|
+
visit(sourceFile);
|
|
849
|
+
}
|
|
850
|
+
|
|
807
851
|
// What the build checks in the bundle it wrote: minify keeps property names,
|
|
808
852
|
// so a bundle that can turn test mode on still says `setTestMode`. Calls with
|
|
809
853
|
// `false` (`!1` once minified) are allowed; any other mention is not - the
|