toolroll 0.9.92 → 0.9.94
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/CHANGELOG.md +87 -0
- package/dist/activity-line.d.ts +7 -4
- package/dist/activity-line.js +9 -7
- package/dist/agent-fence.d.ts +4 -0
- package/dist/agent-fence.js +4 -1
- package/dist/approval-authority.d.ts +7 -1
- package/dist/approval-authority.js +11 -2
- package/dist/approval-password.d.ts +31 -0
- package/dist/approval-password.js +26 -0
- package/dist/assignment-status.d.ts +1 -1
- package/dist/assignment-ui.js +3 -3
- package/dist/assignment.js +16 -3
- package/dist/boot-identity.d.ts +2 -1
- package/dist/boot-identity.js +6 -4
- package/dist/bounded-spawn.d.ts +57 -0
- package/dist/bounded-spawn.js +220 -0
- package/dist/browser/terminal.js +42 -42
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +26 -26
- package/dist/browser-crew.js +2 -2
- package/dist/browser-shell.d.ts +1 -1
- package/dist/browser-shell.js +2 -2
- package/dist/browser-workspace.d.ts +48 -7
- package/dist/browser-workspace.js +3 -3
- package/dist/builder-restart.d.ts +21 -0
- package/dist/builder-restart.js +50 -0
- package/dist/builder.d.ts +4 -0
- package/dist/builder.js +73 -25
- package/dist/chat-acceptance.js +1 -1
- package/dist/chat-actions.d.ts +12 -2
- package/dist/chat-actions.js +25 -2
- package/dist/chat-channel.js +11 -0
- package/dist/chat-decide.d.ts +7 -2
- package/dist/chat-decide.js +37 -9
- package/dist/chat-quiet.js +3 -3
- package/dist/chat-voice.d.ts +1 -1
- package/dist/chat-voice.js +2 -2
- package/dist/check-facts.d.ts +126 -0
- package/dist/check-facts.js +318 -0
- package/dist/check-failure.d.ts +22 -3
- package/dist/check-failure.js +57 -13
- package/dist/check-levels-ui.d.ts +4 -0
- package/dist/check-levels-ui.js +10 -1
- package/dist/check-load.d.ts +85 -0
- package/dist/check-load.js +147 -0
- package/dist/check-only-standing.d.ts +36 -0
- package/dist/check-only-standing.js +39 -0
- package/dist/check-only.d.ts +15 -0
- package/dist/check-only.js +99 -0
- package/dist/check-report.d.ts +7 -0
- package/dist/check-report.js +4 -3
- package/dist/check-stamp.d.ts +46 -0
- package/dist/check-stamp.js +93 -0
- package/dist/checkout-cleanup.d.ts +36 -5
- package/dist/checkout-cleanup.js +166 -14
- package/dist/cli-help.d.ts +4 -1
- package/dist/cli-help.js +47 -30
- package/dist/cli.js +16 -1
- package/dist/code-extract.d.ts +44 -0
- package/dist/code-extract.js +88 -0
- package/dist/code-map-acts.d.ts +24 -0
- package/dist/code-map-acts.js +53 -0
- package/dist/code-map-admission.d.ts +52 -0
- package/dist/code-map-admission.js +295 -0
- package/dist/code-map-build.d.ts +63 -0
- package/dist/code-map-build.js +415 -0
- package/dist/code-map-cli.d.ts +153 -0
- package/dist/code-map-cli.js +103 -0
- package/dist/code-map-main.d.ts +1 -0
- package/dist/code-map-main.js +48 -0
- package/dist/code-map-read.d.ts +106 -0
- package/dist/code-map-read.js +171 -0
- package/dist/code-map-report.d.ts +37 -0
- package/dist/code-map-report.js +95 -0
- package/dist/code-map-shape.d.ts +172 -0
- package/dist/code-map-shape.js +327 -0
- package/dist/code-map-store.d.ts +136 -0
- package/dist/code-map-store.js +233 -0
- package/dist/coding-provider.d.ts +2 -0
- package/dist/coding-provider.js +3 -2
- package/dist/coding-update.js +3 -2
- package/dist/coding-workspace.js +4 -4
- package/dist/console-chrome.js +2 -2
- package/dist/containment.d.ts +5 -0
- package/dist/containment.js +14 -1
- package/dist/contracts/chat-actions.d.ts +9 -0
- package/dist/contracts/chat-actions.js +2 -0
- package/dist/contracts/cli.d.ts +52 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +12 -5
- package/dist/contracts/console-api.js +12 -5
- package/dist/contracts/gateway-tools.d.ts +17 -0
- package/dist/contracts/gateway-tools.js +3 -1
- package/dist/contracts/lead-tools.d.ts +61 -0
- package/dist/contracts/lead-tools.js +10 -1
- package/dist/contracts/project-knowledge.d.ts +38 -5
- package/dist/contracts/project-knowledge.js +11 -5
- package/dist/contracts/project-memory.d.ts +3 -1
- package/dist/contracts/project-memory.js +3 -2
- package/dist/contracts/release-check.d.ts +25 -1
- package/dist/contracts/release-check.js +19 -0
- package/dist/contracts/storage.d.ts +14 -0
- package/dist/contracts/storage.js +3 -1
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/contracts/youiyoux-request.d.ts +1 -1
- package/dist/contracts/youiyoux-request.js +1 -1
- package/dist/desktop-access.js +6 -2
- package/dist/desktop-update.d.ts +2 -2
- package/dist/desktop-update.js +13 -9
- package/dist/dispatch.js +18 -15
- package/dist/dispose.js +8 -2
- package/dist/easy-start.js +2 -3
- package/dist/eval-hooks.d.ts +55 -0
- package/dist/eval-hooks.js +227 -4
- package/dist/evals.js +2 -1
- package/dist/fast-start.d.ts +136 -0
- package/dist/fast-start.js +431 -0
- package/dist/fast-terms.d.ts +9 -0
- package/dist/fast-terms.js +23 -0
- package/dist/fenced-check-coverage.d.ts +62 -0
- package/dist/fenced-check-coverage.js +54 -0
- package/dist/first-run.js +1 -1
- package/dist/flaky-ci.d.ts +31 -0
- package/dist/flaky-ci.js +149 -0
- package/dist/flaky-cli.d.ts +60 -0
- package/dist/flaky-cli.js +60 -0
- package/dist/flaky.d.ts +83 -0
- package/dist/flaky.js +264 -0
- package/dist/flow-card-task.js +3 -0
- package/dist/flow-triggers.d.ts +1 -1
- package/dist/flow-triggers.js +1 -1
- package/dist/github-import.d.ts +62 -0
- package/dist/github-import.js +146 -0
- package/dist/guides.js +15 -10
- package/dist/invoke.d.ts +5 -1
- package/dist/invoke.js +49 -14
- package/dist/job-object-helper.ps1 +10 -3
- package/dist/keymap.d.ts +41 -0
- package/dist/keymap.js +51 -0
- package/dist/knowledge-cli.js +1 -1
- package/dist/knowledge-ui.d.ts +21 -4
- package/dist/knowledge-ui.js +75 -7
- package/dist/laptop-background.d.ts +16 -0
- package/dist/laptop-background.js +108 -2
- package/dist/lead-context.js +1 -1
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +2 -2
- package/dist/lead-progress.js +3 -0
- package/dist/lead-status.d.ts +2 -0
- package/dist/lead-status.js +6 -2
- package/dist/lead-tools.js +51 -2
- package/dist/lead.js +1 -1
- package/dist/lesson-notes.d.ts +81 -0
- package/dist/lesson-notes.js +336 -0
- package/dist/limits-ui.js +1 -1
- package/dist/local-owner.d.ts +1 -1
- package/dist/local-owner.js +11 -0
- package/dist/machine-check.d.ts +16 -0
- package/dist/machine-check.js +26 -0
- package/dist/machine-identity.d.ts +75 -0
- package/dist/machine-identity.js +170 -0
- package/dist/mcp-core.js +11 -1
- package/dist/memory-cli.d.ts +300 -1
- package/dist/memory-cli.js +62 -9
- package/dist/memory-pass.d.ts +16 -0
- package/dist/memory-pass.js +67 -3
- package/dist/memory-tidy.d.ts +181 -0
- package/dist/memory-tidy.js +523 -0
- package/dist/memory-use.d.ts +35 -0
- package/dist/memory-use.js +109 -0
- package/dist/merge-checklist.d.ts +55 -0
- package/dist/merge-checklist.js +182 -0
- package/dist/needs-you.d.ts +32 -13
- package/dist/needs-you.js +108 -49
- package/dist/nightly-github.d.ts +2 -0
- package/dist/nightly-github.js +4 -4
- package/dist/operate-remote-arguments.js +6 -1
- package/dist/operate-remote.js +3 -1
- package/dist/operate.d.ts +6 -1
- package/dist/operate.js +540 -70
- package/dist/phase-routing.js +2 -2
- package/dist/plain-schema.d.ts +23 -0
- package/dist/plain-schema.js +94 -0
- package/dist/plain-words.d.ts +20 -0
- package/dist/plain-words.js +66 -0
- package/dist/planner.js +2 -1
- package/dist/process-liveness.d.ts +2 -0
- package/dist/process-liveness.js +61 -17
- package/dist/project-delete-ui.d.ts +7 -2
- package/dist/project-delete-ui.js +12 -1
- package/dist/project-delete.js +13 -2
- package/dist/project-knowledge.d.ts +18 -11
- package/dist/project-knowledge.js +39 -9
- package/dist/project-learning.d.ts +2 -1
- package/dist/project-learning.js +15 -45
- package/dist/project-memory.d.ts +5 -0
- package/dist/project-memory.js +22 -2
- package/dist/proof.js +30 -5
- package/dist/provider-auth.js +1 -1
- package/dist/publish.js +9 -1
- package/dist/pull-request-flow.js +5 -0
- package/dist/push.d.ts +26 -3
- package/dist/push.js +55 -15
- package/dist/release-adapters.d.ts +9 -0
- package/dist/release-adapters.js +87 -1
- package/dist/release-approval.d.ts +191 -0
- package/dist/release-approval.js +382 -0
- package/dist/release-auto.js +26 -17
- package/dist/release-card.d.ts +74 -6
- package/dist/release-card.js +179 -23
- package/dist/release-carry.d.ts +7 -2
- package/dist/release-carry.js +39 -9
- package/dist/release-cli.d.ts +4 -0
- package/dist/release-cli.js +38 -23
- package/dist/release-coverage.js +1 -1
- package/dist/release-next.d.ts +48 -0
- package/dist/release-next.js +161 -0
- package/dist/release.d.ts +50 -1
- package/dist/release.js +118 -11
- package/dist/remote-destination.d.ts +36 -0
- package/dist/remote-destination.js +217 -0
- package/dist/remote-main.d.ts +48 -0
- package/dist/remote-main.js +84 -5
- package/dist/repository-context.d.ts +3 -2
- package/dist/repository-context.js +112 -70
- package/dist/restart-certification.d.ts +1 -0
- package/dist/restart-certification.js +8 -6
- package/dist/result-acts.d.ts +29 -21
- package/dist/result-acts.js +36 -25
- package/dist/result-follow-ups.d.ts +2 -2
- package/dist/result-follow-ups.js +18 -4
- package/dist/result-review.d.ts +1 -1
- package/dist/route-change-check.d.ts +4 -0
- package/dist/route-change-check.js +13 -0
- package/dist/route-change.d.ts +22 -0
- package/dist/route-change.js +89 -0
- package/dist/route-hooks.js +3 -2
- package/dist/route-results.d.ts +6 -1
- package/dist/route-results.js +14 -5
- package/dist/run-closeout.d.ts +1 -1
- package/dist/run-closeout.js +7 -5
- package/dist/runner-process.js +3 -2
- package/dist/runner.js +2 -1
- package/dist/scope.d.ts +6 -1
- package/dist/scope.js +22 -9
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +24 -18
- package/dist/server/chat.js +14 -12
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +26 -35
- package/dist/server/console.css +23 -0
- package/dist/server/guards.js +4 -0
- package/dist/server/home.d.ts +6 -0
- package/dist/server/home.js +21 -13
- package/dist/server/http.js +1 -1
- package/dist/server/kit.js +1 -1
- package/dist/server/merge-ready-view.d.ts +10 -0
- package/dist/server/merge-ready-view.js +30 -0
- package/dist/server/pages-assets.d.ts +1 -0
- package/dist/server/pages-assets.js +8 -5
- package/dist/server/pages.js +103 -82
- package/dist/server/render-chat.js +2 -2
- package/dist/server/render-code-map.d.ts +30 -0
- package/dist/server/render-code-map.js +137 -0
- package/dist/server/render-flaky.d.ts +20 -0
- package/dist/server/render-flaky.js +59 -0
- package/dist/server/render-history.js +1 -1
- package/dist/server/render-new-task.d.ts +26 -1
- package/dist/server/render-new-task.js +30 -5
- package/dist/server/render-pages.d.ts +1 -175
- package/dist/server/render-pages.js +38 -313
- package/dist/server/render-projects.d.ts +2 -1
- package/dist/server/render-projects.js +9 -7
- package/dist/server/render-release.d.ts +19 -0
- package/dist/server/render-release.js +84 -0
- package/dist/server/render-results.d.ts +2 -2
- package/dist/server/render-results.js +71 -73
- package/dist/server/render-tasks.d.ts +13 -2
- package/dist/server/render-tasks.js +156 -139
- package/dist/server/retired-routes.d.ts +17 -0
- package/dist/server/retired-routes.js +53 -0
- package/dist/server/route-table.d.ts +4 -1
- package/dist/server/route-table.js +52 -13
- package/dist/server/settings.js +129 -22
- package/dist/server/task-views.js +61 -26
- package/dist/server/tasks.d.ts +17 -6
- package/dist/server/tasks.js +273 -338
- package/dist/server/welcome.js +1 -1
- package/dist/settings-groups-ui.js +1 -1
- package/dist/setup-guide.d.ts +2 -2
- package/dist/stop-proof.d.ts +67 -0
- package/dist/stop-proof.js +295 -0
- package/dist/storage-sweep-process.js +1 -1
- package/dist/storage-sweep.d.ts +5 -3
- package/dist/storage-sweep.js +30 -9
- package/dist/storage-ui.js +28 -17
- package/dist/storage.d.ts +27 -2
- package/dist/storage.js +39 -2
- package/dist/store.d.ts +148 -34
- package/dist/store.js +626 -177
- package/dist/surface.js +46 -5
- package/dist/sweep-quarantine.d.ts +1 -0
- package/dist/sweep-quarantine.js +6 -4
- package/dist/tamper-scan.d.ts +4 -0
- package/dist/tamper-scan.js +90 -0
- package/dist/task-aliases.d.ts +5 -0
- package/dist/task-aliases.js +9 -0
- package/dist/task-control.js +4 -1
- package/dist/task-outcome-cli.d.ts +2 -0
- package/dist/task-outcome-cli.js +36 -17
- package/dist/task-sizing.d.ts +5 -0
- package/dist/task-sizing.js +2 -0
- package/dist/task-source.d.ts +7 -0
- package/dist/task-source.js +14 -0
- package/dist/task-status.d.ts +9 -0
- package/dist/task-status.js +40 -49
- package/dist/teardown-git.d.ts +16 -0
- package/dist/teardown-git.js +63 -5
- package/dist/telegram-progress.js +5 -3
- package/dist/telegram-status.js +6 -4
- package/dist/test-infra.d.ts +8 -0
- package/dist/test-infra.js +18 -0
- package/dist/tidy.js +1 -1
- package/dist/toolroll-update-ui.js +2 -2
- package/dist/toolroll-update.js +18 -5
- package/dist/tui-contract.d.ts +24 -0
- package/dist/tui-source.js +67 -8
- package/dist/tui.js +51 -51
- package/dist/vanished-retry.d.ts +30 -0
- package/dist/vanished-retry.js +174 -0
- package/dist/work-index.d.ts +2 -0
- package/dist/work-index.js +82 -70
- package/dist/work-summary.d.ts +2 -2
- package/dist/work-summary.js +2 -0
- package/dist/workspace-ui.d.ts +4 -2
- package/dist/workspace-ui.js +28 -22
- package/dist/worktree.d.ts +12 -3
- package/dist/worktree.js +68 -13
- package/package.json +1 -1
- package/dist/server/render-next.d.ts +0 -63
- package/dist/server/render-next.js +0 -109
package/dist/invoke.js
CHANGED
|
@@ -27,7 +27,7 @@ import { attestProvider } from "./attest.js";
|
|
|
27
27
|
import { run as runCommand, runOwnerTag, streamsInput } from "./exec.js";
|
|
28
28
|
import { spawnSync } from "node:child_process";
|
|
29
29
|
import { currentContainment } from "./containment.js";
|
|
30
|
-
import { recordRunBillingBasis, recordRunCostUnknown, recordRunModelReported } from "./store.js";
|
|
30
|
+
import { recordCheckStamp, recordRunBillingBasis, recordRunCostUnknown, recordRunModelReported } from "./store.js";
|
|
31
31
|
import { witnessedRunner } from "./process-custody.js";
|
|
32
32
|
import { underStopWatch } from "./task-control.js";
|
|
33
33
|
import { childDatabaseEnv, isolatedChildDatabase, removeChildDatabase as removeAgentDatabase } from "./child-database.js";
|
|
@@ -39,10 +39,12 @@ import { dirname, join } from "node:path";
|
|
|
39
39
|
import { poolRootOf } from "./worktree.js";
|
|
40
40
|
import { checkLaunchRoot, signInGrants, signInIsItself } from "./agent-fence.js";
|
|
41
41
|
import { localModeOn } from "./local-owner.js";
|
|
42
|
+
import { CHECK_STAMP_VERSION, EVERYWHERE, unfencedStamp } from "./check-stamp.js";
|
|
42
43
|
import { CHECK_ACCESS_DEFAULT, checkAccessHere, localModeCap, narrowestAccess, narrowestNetwork, signInsAvailable } from "./check-access.js";
|
|
43
44
|
import { readDenyPath } from "./project-sandbox.js";
|
|
44
45
|
import { sandboxRuntimeWords } from "./sandbox-runtime.js";
|
|
45
46
|
import { canonicalJson } from "./canonical-json.js";
|
|
47
|
+
import { deliveredFile } from "./code-map-admission.js";
|
|
46
48
|
import { buildCacheEnv, buildCacheFolder, grantFolder, writeAllowlist, WRITES_DEFAULT, CheckoutBindingError, trustedWorktreeBinding, checkoutGitEnv, HARDENED_GIT_ARGS, codexFenceArgv, fenceFor, fencedGitEnv, LEGACY_SANDBOX, launchSandboxOf, linuxFenceAvailable, macosFenceAvailable, NO_GRANTS, sandboxedSpawn, serverGrantsOf, runFolders, taskBoundary } from "./agent-fence.js";
|
|
47
49
|
import { proxyEnv, providerOnlyNetwork, repositoryEcosystems, resolveNetwork, startEgressProxy } from "./egress.js";
|
|
48
50
|
import { claudeConfigDir, claudeDefaults, codexDefaults, codexHomeDir, isolatedCodexHome } from "./provider-home.js";
|
|
@@ -960,6 +962,8 @@ function runFence(store, runId, provider, keyHome, ownTools, writes = WRITES_DEF
|
|
|
960
962
|
// checkout was recorded in (a --pool elsewhere), so another project's worktree, present at launch, is out of reach. Native wrappers also cover later creations.
|
|
961
963
|
const launch = fenceFor(provider === "command" ? "command" : "build", { databaseFile, worktree, ...home, boundary, binding, runFolders: folders, poolRoots,
|
|
962
964
|
extra: allowed?.deny ?? [], linked: linkedFor(store, worktree), writes: allowed === null ? null : { writes: allowed.writes, readOnly: allowed.readOnly }, policy: sandbox.policy, provider,
|
|
965
|
+
// This launch's code map file: exactly that file, read-only, inside the denied state folder. Commands don't get it.
|
|
966
|
+
...(provider === "command" ? {} : { exactFile: deliveredFile(store, runId) }),
|
|
963
967
|
// The sealed read denies, and whatever the project lists now too (adding a deny only narrows).
|
|
964
968
|
readDeny: [...new Set([...sandbox.readDeny, ...(row === null ? [] : store.projectSandbox(row.repo).readDeny)])].map(one => readDenyPath(one, keyHome ?? homedir())), machLookup: sandbox.machLookup });
|
|
965
969
|
return {
|
|
@@ -992,6 +996,7 @@ export function fencedCommands(store, runId, writes, inner, available = () => ma
|
|
|
992
996
|
if (!available()) {
|
|
993
997
|
if (store.unfencedAgentsAllowed()) {
|
|
994
998
|
store.stampRunCheckAccess(runId, "unsandboxed");
|
|
999
|
+
stampLaunch(store, [runId], unfencedStamp());
|
|
995
1000
|
return inner(file, args, options);
|
|
996
1001
|
}
|
|
997
1002
|
return { code: 1, stdout: "", stderr: "This machine can't confine the build's commands (no macOS sandbox, no working bubblewrap), so they didn't run. Install bubblewrap, socat and ripgrep (Linux), or an instance operator allows unconfined agents with `toolroll config unconfined-agents allow`.\n", timedOut: false, notFound: false };
|
|
@@ -1002,13 +1007,17 @@ export function fencedCommands(store, runId, writes, inner, available = () => ma
|
|
|
1002
1007
|
mkdirSync(temp, { mode: 0o700 });
|
|
1003
1008
|
// Not sandboxed (check-access.ts): the project's checks are trusted by the whole instance, as before 0.9.66. Never
|
|
1004
1009
|
// while local mode is on (easy-start C5): then they run in the fence, sandboxed.
|
|
1005
|
-
if (access === "unsandboxed" && !localModeOn(store))
|
|
1010
|
+
if (access === "unsandboxed" && !localModeOn(store)) {
|
|
1011
|
+
stampLaunch(store, [runId], unfencedStamp());
|
|
1006
1012
|
return await inner(file, args, { ...options, env: { ...(options?.env ?? {}), TMPDIR: temp, TMP: temp, TEMP: temp } });
|
|
1013
|
+
}
|
|
1007
1014
|
const fenced = runFence(store, runId, "command", undefined, own, writes, sandbox);
|
|
1008
1015
|
if (fenced.problem !== undefined)
|
|
1009
1016
|
return { code: 1, stdout: "", stderr: `${fenced.problem}\n`, timedOut: false, notFound: false };
|
|
1010
|
-
if (fenced.fence.length === 0)
|
|
1017
|
+
if (fenced.fence.length === 0) {
|
|
1018
|
+
stampLaunch(store, [runId], unfencedStamp());
|
|
1011
1019
|
return inner(file, args, options);
|
|
1020
|
+
}
|
|
1012
1021
|
// A self-hosted check's own database (child-database.ts) is the command's to write too.
|
|
1013
1022
|
const database = options?.env?.["TOOLROLL_DB"];
|
|
1014
1023
|
const checkData = database === undefined || fenced.grants.writes === undefined ? null : grantFolder(dirname(database), false);
|
|
@@ -1020,12 +1029,14 @@ export function fencedCommands(store, runId, writes, inner, available = () => ma
|
|
|
1020
1029
|
if (signIns !== null && "problem" in signIns)
|
|
1021
1030
|
return { code: 1, stdout: "", stderr: `${signIns.problem}\n`, timedOut: false, notFound: false };
|
|
1022
1031
|
try {
|
|
1032
|
+
// Each sign-in file looked at again right before the launch: one no longer itself isn't granted.
|
|
1033
|
+
// Never the keychain: the sign-ins kept there come in the environment, read outside (keychainSignIns).
|
|
1034
|
+
const given = signIns === null ? grants : { ...grants, signIns: { ...signIns.grants, read: signIns.grants.read.filter(path => signInIsItself(realpathSync(homedir()), path)) }, network: signIns.network };
|
|
1035
|
+
stampLaunch(store, [runId], fencedStamp(signIns === null ? "sandboxed" : "sign-ins", fenced.fence, given, signIns === null ? undefined : network));
|
|
1023
1036
|
return await inner(file, args, {
|
|
1024
1037
|
...options,
|
|
1025
1038
|
fence: fenced.fence,
|
|
1026
|
-
|
|
1027
|
-
// Never the keychain: the sign-ins kept there come in the environment, read outside (keychainSignIns).
|
|
1028
|
-
fenceGrants: signIns === null ? grants : { ...grants, signIns: { ...signIns.grants, read: signIns.grants.read.filter(path => signInIsItself(realpathSync(homedir()), path)) }, network: signIns.network },
|
|
1039
|
+
fenceGrants: given,
|
|
1029
1040
|
confinement: "required",
|
|
1030
1041
|
env: { ...(options?.env ?? {}), TMPDIR: temp, TMP: temp, TEMP: temp, ...(fenced.cache === undefined || fenced.grants.writes === undefined ? {} : buildCacheEnv(fenced.cache)), ...(signIns?.env ?? {}) },
|
|
1031
1042
|
...(signIns === null ? {} : { omitEnv: [...(options?.omitEnv ?? []), ...SIGN_INS_OMITTED_ENV] }),
|
|
@@ -1046,26 +1057,32 @@ export function fencedCommands(store, runId, writes, inner, available = () => ma
|
|
|
1046
1057
|
* project's writes setting, only that checkout, its Git data, its own temp folder, the project's caches and its check
|
|
1047
1058
|
* database writable. Where nothing can confine them, they run only when an instance operator allowed unconfined agents.
|
|
1048
1059
|
*/
|
|
1049
|
-
export function fencedCheckoutCommands(store, repo, checkout, inner, available = () => macosFenceAvailable() || linuxFenceAvailable(), access = CHECK_ACCESS_DEFAULT, network, sandbox = LEGACY_SANDBOX) {
|
|
1060
|
+
export function fencedCheckoutCommands(store, repo, checkout, inner, available = () => macosFenceAvailable() || linuxFenceAvailable(), access = CHECK_ACCESS_DEFAULT, network, sandbox = LEGACY_SANDBOX, stampRuns = []) {
|
|
1050
1061
|
if (inner !== runCommand)
|
|
1051
1062
|
return inner;
|
|
1052
1063
|
return async (file, args, options) => {
|
|
1053
1064
|
if (!available()) {
|
|
1054
|
-
if (store.unfencedAgentsAllowed())
|
|
1065
|
+
if (store.unfencedAgentsAllowed()) {
|
|
1066
|
+
stampLaunch(store, stampRuns, unfencedStamp());
|
|
1055
1067
|
return inner(file, args, options);
|
|
1068
|
+
}
|
|
1056
1069
|
return { code: 1, stdout: "", stderr: "This machine can't confine the project's checks (no macOS sandbox, no working bubblewrap), so they didn't run. Install bubblewrap, socat and ripgrep (Linux), or an instance operator allows unconfined agents with `toolroll config unconfined-agents allow`.\n", timedOut: false, notFound: false };
|
|
1057
1070
|
}
|
|
1058
1071
|
const databaseFile = store.databaseFile();
|
|
1059
|
-
if (databaseFile === null)
|
|
1072
|
+
if (databaseFile === null) {
|
|
1073
|
+
stampLaunch(store, stampRuns, unfencedStamp());
|
|
1060
1074
|
return inner(file, args, options);
|
|
1075
|
+
}
|
|
1061
1076
|
const own = realpathSync(mkdtempSync(join(checkLaunchRoot(), "so-cmd-")));
|
|
1062
1077
|
try {
|
|
1063
1078
|
const temp = join(own, "tmp");
|
|
1064
1079
|
mkdirSync(temp, { mode: 0o700 });
|
|
1065
1080
|
// Not sandboxed (check-access.ts): the project's checks are trusted by the whole instance, as before 0.9.66. Never
|
|
1066
1081
|
// while local mode is on (easy-start C5): then they run in the fence, sandboxed.
|
|
1067
|
-
if (access === "unsandboxed" && !localModeOn(store))
|
|
1082
|
+
if (access === "unsandboxed" && !localModeOn(store)) {
|
|
1083
|
+
stampLaunch(store, stampRuns, unfencedStamp());
|
|
1068
1084
|
return await inner(file, args, { ...options, env: { ...(options?.env ?? {}), TMPDIR: temp, TMP: temp, TEMP: temp } });
|
|
1085
|
+
}
|
|
1069
1086
|
let binding;
|
|
1070
1087
|
try {
|
|
1071
1088
|
binding = trustedWorktreeBinding(repo, checkout);
|
|
@@ -1091,12 +1108,14 @@ export function fencedCheckoutCommands(store, repo, checkout, inner, available =
|
|
|
1091
1108
|
if (signIns !== null && "problem" in signIns)
|
|
1092
1109
|
return { code: 1, stdout: "", stderr: `${signIns.problem}\n`, timedOut: false, notFound: false };
|
|
1093
1110
|
try {
|
|
1111
|
+
// Each sign-in file looked at again right before the launch: one no longer itself isn't granted.
|
|
1112
|
+
// Never the keychain: the sign-ins kept there come in the environment, read outside (keychainSignIns).
|
|
1113
|
+
const given = signIns === null ? grants : { ...grants, signIns: { ...signIns.grants, read: signIns.grants.read.filter(path => signInIsItself(realpathSync(homedir()), path)) }, network: signIns.network };
|
|
1114
|
+
stampLaunch(store, stampRuns, fencedStamp(signIns === null ? "sandboxed" : "sign-ins", launch.fence, given, signIns === null ? undefined : network));
|
|
1094
1115
|
return await inner(file, args, {
|
|
1095
1116
|
...options,
|
|
1096
1117
|
fence: launch.fence,
|
|
1097
|
-
|
|
1098
|
-
// Never the keychain: the sign-ins kept there come in the environment, read outside (keychainSignIns).
|
|
1099
|
-
fenceGrants: signIns === null ? grants : { ...grants, signIns: { ...signIns.grants, read: signIns.grants.read.filter(path => signInIsItself(realpathSync(homedir()), path)) }, network: signIns.network },
|
|
1118
|
+
fenceGrants: given,
|
|
1100
1119
|
confinement: launch.confinement,
|
|
1101
1120
|
env: { ...(options?.env ?? {}), TMPDIR: temp, TMP: temp, TEMP: temp, ...(allowed === null ? {} : buildCacheEnv(cache)), ...(signIns?.env ?? {}) },
|
|
1102
1121
|
...(signIns === null ? {} : { omitEnv: [...(options?.omitEnv ?? []), ...SIGN_INS_OMITTED_ENV] }),
|
|
@@ -1284,7 +1303,7 @@ export function checkoutRunner(store, repo, checkout, inner, runIds, ran, platfo
|
|
|
1284
1303
|
return (file, args, options) => {
|
|
1285
1304
|
const launch = checkoutLaunchOf(store, repo, runIds, platform);
|
|
1286
1305
|
ran(actualCheckAccess(store, launch.access));
|
|
1287
|
-
return fencedCheckoutCommands(store, repo, checkout, inner, undefined, launch.access, launch.network, checkoutSandboxOf(store, repo, runIds))(file, args, options);
|
|
1306
|
+
return fencedCheckoutCommands(store, repo, checkout, inner, undefined, launch.access, launch.network, checkoutSandboxOf(store, repo, runIds), runIds)(file, args, options);
|
|
1288
1307
|
};
|
|
1289
1308
|
}
|
|
1290
1309
|
/**
|
|
@@ -1308,6 +1327,22 @@ export function checkoutSandboxOf(store, repo, runIds) {
|
|
|
1308
1327
|
machLookup: common(one => one.machLookup, live.machLookup),
|
|
1309
1328
|
};
|
|
1310
1329
|
}
|
|
1330
|
+
/** A fenced launch's stamp: the level it runs at and exactly what it was given (check-stamp.ts), read from the grants the
|
|
1331
|
+
* launch itself takes, never from what was asked for. */
|
|
1332
|
+
export function fencedStamp(level, fence, grants, network) {
|
|
1333
|
+
const writes = grants.writes;
|
|
1334
|
+
return {
|
|
1335
|
+
version: CHECK_STAMP_VERSION, level, policyDigest: fenceDigest(fence, grants),
|
|
1336
|
+
writable: writes === undefined ? [EVERYWHERE] : [...new Set([...writes.subpaths, ...writes.prefixes.map(one => `${one.path}${one.suffix}`)])].sort(),
|
|
1337
|
+
readable: [...new Set([...grants.readable, ...(grants.signIns?.read ?? [])])].sort(),
|
|
1338
|
+
network: network === undefined ? { mode: "machine" } : { mode: network.mode, hosts: [...network.hosts].sort() },
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1341
|
+
function stampLaunch(store, runs, stamp) {
|
|
1342
|
+
const now = new Date();
|
|
1343
|
+
for (const runId of runs)
|
|
1344
|
+
recordCheckStamp(store, runId, stamp, now);
|
|
1345
|
+
}
|
|
1311
1346
|
/** The digest of a launch's whole fence (its denied paths and grants, canonical), as stamped on its run (C11). */
|
|
1312
1347
|
export function fenceDigest(fence, grants) {
|
|
1313
1348
|
return createHash("sha256").update(`toolroll:fence:${canonicalJson({ fence: [...fence], grants: grants })}`).digest("hex").slice(0, 32);
|
|
@@ -25,7 +25,9 @@ param(
|
|
|
25
25
|
# a space or a percent sign the controller passed.
|
|
26
26
|
[Parameter(Position = 1)] [string] $Encoded,
|
|
27
27
|
[string] $JobName,
|
|
28
|
-
[string] $QueryName
|
|
28
|
+
[string] $QueryName,
|
|
29
|
+
# A whole-job memory limit in bytes (the code map's bounded reads); 0 is none.
|
|
30
|
+
[long] $MemoryLimit = 0
|
|
29
31
|
)
|
|
30
32
|
|
|
31
33
|
$ErrorActionPreference = "Stop"
|
|
@@ -68,6 +70,7 @@ public static class SoJob {
|
|
|
68
70
|
public const int JobObjectBasicAccountingInformation = 1;
|
|
69
71
|
public const int JobObjectExtendedLimitInformation = 9;
|
|
70
72
|
public const uint JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE = 0x2000;
|
|
73
|
+
public const uint JOB_OBJECT_LIMIT_JOB_MEMORY = 0x200;
|
|
71
74
|
public const uint CREATE_SUSPENDED = 0x4;
|
|
72
75
|
public const uint CREATE_UNICODE_ENVIRONMENT = 0x400;
|
|
73
76
|
public const int STARTF_USESTDHANDLES = 0x100;
|
|
@@ -111,7 +114,7 @@ public static class SoJob {
|
|
|
111
114
|
catch { return "unknown"; }
|
|
112
115
|
finally { CloseHandle(job); }
|
|
113
116
|
}
|
|
114
|
-
public static IntPtr MakeJob(string name) {
|
|
117
|
+
public static IntPtr MakeJob(string name, long memoryLimit) {
|
|
115
118
|
IntPtr job = CreateJobObjectW(IntPtr.Zero, name);
|
|
116
119
|
int createdError = Marshal.GetLastWin32Error();
|
|
117
120
|
if (job != IntPtr.Zero && createdError == 183) { CloseHandle(job); throw new Exception("job name already exists"); }
|
|
@@ -119,6 +122,10 @@ public static class SoJob {
|
|
|
119
122
|
var info = new JOBOBJECT_EXTENDED_LIMIT_INFORMATION();
|
|
120
123
|
// Kill on close; NO breakaway flags, so nothing inside may leave the job.
|
|
121
124
|
info.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
|
|
125
|
+
if (memoryLimit > 0) {
|
|
126
|
+
info.BasicLimitInformation.LimitFlags |= JOB_OBJECT_LIMIT_JOB_MEMORY;
|
|
127
|
+
info.JobMemoryLimit = new UIntPtr((ulong)memoryLimit);
|
|
128
|
+
}
|
|
122
129
|
int size = Marshal.SizeOf(typeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION));
|
|
123
130
|
IntPtr buffer = Marshal.AllocHGlobal(size);
|
|
124
131
|
try {
|
|
@@ -177,7 +184,7 @@ try {
|
|
|
177
184
|
[string[]]$targetArguments = ConvertFrom-Json -InputObject ([System.Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($Encoded)))
|
|
178
185
|
if ($targetArguments.Count -lt 1) { throw "the target argv is empty" }
|
|
179
186
|
# Admission arrives only after the controller durably records this helper.
|
|
180
|
-
$job = [SoJob]::MakeJob($JobName)
|
|
187
|
+
$job = [SoJob]::MakeJob($JobName, $MemoryLimit)
|
|
181
188
|
Send "ready"
|
|
182
189
|
$admission = $reader.ReadLineAsync()
|
|
183
190
|
if (-not $admission.Wait(15000) -or $admission.Result -cne "go") { throw "job admission refused" }
|
package/dist/keymap.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** One keymap for the console and the terminal UI (docs/plans/plain-toolroll.md P1.7). A shortcut only ever presses a
|
|
2
|
+
* control the page already rendered, the same press a person's click makes, so any password field or confirm it opens
|
|
3
|
+
* still appears; with no such control on the page the key does nothing. Shortcuts sleep in text fields, with a
|
|
4
|
+
* modifier held, and while the shortcuts list is open. Navigation keys go to a page and post nothing. */
|
|
5
|
+
/** What a key does in the console. `press`: the first visible, enabled control matching these, in order. */
|
|
6
|
+
export type WebShortcut = {
|
|
7
|
+
kind: "go";
|
|
8
|
+
href: string;
|
|
9
|
+
} | {
|
|
10
|
+
kind: "press";
|
|
11
|
+
selectors: readonly string[];
|
|
12
|
+
} | {
|
|
13
|
+
kind: "search";
|
|
14
|
+
} | {
|
|
15
|
+
kind: "help";
|
|
16
|
+
};
|
|
17
|
+
/** What a key does in the terminal UI, or null with the reason it has none there (a named gap). */
|
|
18
|
+
export type TuiShortcut = "new" | "ink" | "send-back" | "retry" | "list" | "help";
|
|
19
|
+
export type Shortcut = {
|
|
20
|
+
keys: string;
|
|
21
|
+
says: string;
|
|
22
|
+
web: WebShortcut;
|
|
23
|
+
tui: TuiShortcut | {
|
|
24
|
+
none: string;
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
/** The page's ink act: Approve (the approve control itself, password popover and all) or Accept. */
|
|
28
|
+
export declare const INK_ACT_SELECTORS: readonly ["[data-ink-approve]", '[data-ink-act="accept"]', "[data-accept-result][data-primary-action]"];
|
|
29
|
+
/** Send back: the control that opens the Send back form (never its submit: the note comes first). */
|
|
30
|
+
export declare const SEND_BACK_SELECTORS: readonly ['[data-act="request-changes"]', '[data-act="revise"]', 'a.result-feedback-link[href="#request-changes"]'];
|
|
31
|
+
/** Retry: the result's Retry, or the failed box's own Retry form. */
|
|
32
|
+
export declare const RETRY_SELECTORS: readonly ['[data-act="retry"]', 'form[data-retry] button[type="submit"]', 'form[data-rebuild] button[type="submit"]'];
|
|
33
|
+
export declare const SHORTCUTS: readonly Shortcut[];
|
|
34
|
+
/** The keys typed, as the help list shows them: "g h" reads "g then h". */
|
|
35
|
+
export declare const keyWords: (keys: string) => string;
|
|
36
|
+
/**
|
|
37
|
+
* The console's key routing, as an inline script (it runs inside chromeScript's closure, which defines `show` for the
|
|
38
|
+
* jump-to palette, `toggleHelp`, `helpOpen`, `close` and `go`). Generated from SHORTCUTS, so the table is the only
|
|
39
|
+
* place a key is defined.
|
|
40
|
+
*/
|
|
41
|
+
export declare function webKeymapScript(): string;
|
package/dist/keymap.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/** One keymap for the console and the terminal UI (docs/plans/plain-toolroll.md P1.7). A shortcut only ever presses a
|
|
2
|
+
* control the page already rendered, the same press a person's click makes, so any password field or confirm it opens
|
|
3
|
+
* still appears; with no such control on the page the key does nothing. Shortcuts sleep in text fields, with a
|
|
4
|
+
* modifier held, and while the shortcuts list is open. Navigation keys go to a page and post nothing. */
|
|
5
|
+
/** The page's ink act: Approve (the approve control itself, password popover and all) or Accept. */
|
|
6
|
+
export const INK_ACT_SELECTORS = ["[data-ink-approve]", '[data-ink-act="accept"]', "[data-accept-result][data-primary-action]"];
|
|
7
|
+
/** Send back: the control that opens the Send back form (never its submit: the note comes first). */
|
|
8
|
+
export const SEND_BACK_SELECTORS = ['[data-act="request-changes"]', '[data-act="revise"]', 'a.result-feedback-link[href="#request-changes"]'];
|
|
9
|
+
/** Retry: the result's Retry, or the failed box's own Retry form. */
|
|
10
|
+
export const RETRY_SELECTORS = ['[data-act="retry"]', 'form[data-retry] button[type="submit"]', 'form[data-rebuild] button[type="submit"]'];
|
|
11
|
+
export const SHORTCUTS = [
|
|
12
|
+
{ keys: "n", says: "new task", web: { kind: "go", href: "/tasks/new" }, tui: "new" },
|
|
13
|
+
{ keys: "a", says: "Approve or Accept (presses the button on the page)", web: { kind: "press", selectors: INK_ACT_SELECTORS }, tui: "ink" },
|
|
14
|
+
{ keys: "b", says: "Send back (opens its note)", web: { kind: "press", selectors: SEND_BACK_SELECTORS }, tui: "send-back" },
|
|
15
|
+
{ keys: "r", says: "Retry (presses the button on the page)", web: { kind: "press", selectors: RETRY_SELECTORS }, tui: "retry" },
|
|
16
|
+
{ keys: "/", says: "search", web: { kind: "search" }, tui: { none: "The terminal has no search; press w to open the console." } },
|
|
17
|
+
{ keys: "g h", says: "go Home", web: { kind: "go", href: "/" }, tui: "list" },
|
|
18
|
+
{ keys: "g t", says: "go to Tasks", web: { kind: "go", href: "/tasks" }, tui: "list" },
|
|
19
|
+
{ keys: "?", says: "these shortcuts", web: { kind: "help" }, tui: "help" },
|
|
20
|
+
];
|
|
21
|
+
/** The keys typed, as the help list shows them: "g h" reads "g then h". */
|
|
22
|
+
export const keyWords = (keys) => keys.split(" ").join(" then ");
|
|
23
|
+
/**
|
|
24
|
+
* The console's key routing, as an inline script (it runs inside chromeScript's closure, which defines `show` for the
|
|
25
|
+
* jump-to palette, `toggleHelp`, `helpOpen`, `close` and `go`). Generated from SHORTCUTS, so the table is the only
|
|
26
|
+
* place a key is defined.
|
|
27
|
+
*/
|
|
28
|
+
export function webKeymapScript() {
|
|
29
|
+
const single = {}, after = {};
|
|
30
|
+
for (const one of SHORTCUTS) {
|
|
31
|
+
const parts = one.keys.split(" ");
|
|
32
|
+
if (parts.length === 1)
|
|
33
|
+
single[parts[0]] = one.web;
|
|
34
|
+
else if (parts[0] === "g")
|
|
35
|
+
after[parts[1]] = one.web;
|
|
36
|
+
}
|
|
37
|
+
return `var KEYS=${JSON.stringify(single)},GKEYS=${JSON.stringify(after)};` +
|
|
38
|
+
// A control a person could press right now: rendered, shown, and not disabled.
|
|
39
|
+
`function pressable(el){if(!el||el.disabled||el.getAttribute("aria-disabled")==="true")return false;` +
|
|
40
|
+
`if(el.closest("[hidden],[inert]"))return false;var r=el.getClientRects();return r.length>0;}` +
|
|
41
|
+
// Press it exactly as a click would; a submit whose form isn't complete (an empty password, a required reason)
|
|
42
|
+
// shows the browser's own prompt for it instead of posting.
|
|
43
|
+
`function press(selectors){for(var s=0;s<selectors.length;s++){var all=document.querySelectorAll(selectors[s]);` +
|
|
44
|
+
`for(var i=0;i<all.length;i++){var el=all[i];if(!pressable(el))continue;` +
|
|
45
|
+
`var form=el.form||null;var submits=el.tagName.toLowerCase()==="button"&&(el.type==="submit"||el.getAttribute("type")===null)&&form;` +
|
|
46
|
+
`if(submits&&!form.checkValidity()){form.reportValidity();return true;}` +
|
|
47
|
+
`el.click();return true;}}return false;}` +
|
|
48
|
+
`function search(){if(document.querySelector("[data-workspace-shell]")){window.dispatchEvent(new CustomEvent("so:open-search",{detail:document.activeElement}));return;}show();}` +
|
|
49
|
+
`function run(act){if(act.kind==="go"){go(act.href);return true;}if(act.kind==="press")return press(act.selectors);` +
|
|
50
|
+
`if(act.kind==="search"){search();return true;}if(act.kind==="help"){toggleHelp();return true;}return false;}`;
|
|
51
|
+
}
|
package/dist/knowledge-cli.js
CHANGED
|
@@ -112,7 +112,7 @@ export async function runKnowledgeCommand(positional, options, context) {
|
|
|
112
112
|
admittedToken = token;
|
|
113
113
|
}
|
|
114
114
|
const cacheRoot = context.evidenceRoot ? join(dirname(context.evidenceRoot), 'repository-context') : namedPath(join(homedir(), '.cache'), ['repository-context']);
|
|
115
|
-
const result = await repositoryContext({ repo: resolve(repo), query, mode: spec.action === 'impact' ? 'impact' : 'search', refresh: spec.action === 'refresh', cacheRoot, ...(typeof base === 'string' ? { baseRevision: base } : {}) });
|
|
115
|
+
const result = await repositoryContext({ repo: resolve(repo), ...(admittedToken ? { project: resolve(repo) } : {}), query, mode: spec.action === 'impact' ? 'impact' : 'search', refresh: spec.action === 'refresh', cacheRoot, ...(typeof base === 'string' ? { baseRevision: base } : {}) });
|
|
116
116
|
if (admittedToken) {
|
|
117
117
|
const current = authenticateCoordinator(context.store, admittedToken);
|
|
118
118
|
if (!current.ok)
|
package/dist/knowledge-ui.d.ts
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
import { type KnowledgeDraft, type KnowledgeSelection, type KnowledgeView } from './project-knowledge.js';
|
|
2
2
|
import { type Html } from './html.js';
|
|
3
|
+
import { type MemoryUse } from './memory-use.js';
|
|
3
4
|
/** A section as a frame (after ReUI's Frame, MIT): its heading on the raised well, its body one paper panel. The
|
|
4
5
|
* cards inside become hairline-divided rows of that panel, never cards in a card. */
|
|
5
6
|
export declare function framedSection(heading: string, body: Html | string, extra?: string): Html;
|
|
6
7
|
export declare const KNOWLEDGE_CSS = ".knowledge{max-width:780px;min-width:0;overflow-wrap:anywhere}.knowledge .card{padding:20px;min-width:0;margin:16px 0}.knowledge h2{margin:0 0 12px}.knowledge form{display:grid;gap:12px;margin:0}.knowledge label{display:grid;gap:6px}.knowledge textarea,.knowledge input,.knowledge select{box-sizing:border-box;width:100%;min-width:0;max-width:100%}.knowledge textarea{resize:vertical;min-height:110px}.knowledge button,.knowledge summary,.knowledge .button-link{min-height:44px}.knowledge button{justify-self:start;white-space:nowrap}.knowledge summary{padding:12px 0;cursor:pointer}.knowledge pre{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit}.knowledge .knowledge-actions{display:flex;gap:12px;flex-wrap:wrap}.knowledge .meta{font-size:.86rem}.knowledge .knowledge-source{display:block}.knowledge details+details{border-top:1px solid var(--so-line)}.memory-stats{display:flex;flex-wrap:wrap;gap:.4rem}.memory-stats span+span::before{content:\"\u00B7\";margin-right:.4rem}.knowledge.frame{margin:0 0 16px}.knowledge.frame>.frame-head h2,.knowledge.frame>summary.frame-head{margin:0;font-size:13px;font-weight:600;line-height:1.4}.knowledge.frame>summary.frame-head{min-height:44px;padding:4px 10px}.knowledge.frame>.frame-panel{min-width:0}.knowledge.frame>.frame-panel>:first-child{margin-top:0}.knowledge.frame>.frame-panel>:last-child{margin-bottom:0}.knowledge .frame-panel>details.card,.knowledge .frame-panel .learning .card{margin:0;padding:0;border:0;border-radius:0;background:none;box-shadow:none}.knowledge .frame-panel>details.card+details.card,.knowledge .frame-panel .learning .card+.card{border-top:1px solid var(--so-line);padding-top:4px}.knowledge.frame>summary.frame-head{justify-content:flex-start}.knowledge form.field-row{grid-template-columns:minmax(0,1fr) auto;align-items:end}.knowledge form.field-row>label,.knowledge form.field-row :is(input,select,button){margin:0}@media(max-width:600px){.knowledge .card{padding:16px}.knowledge h2{font-size:1.1rem}}";
|
|
7
|
-
export declare function knowledgeHtml(view: KnowledgeView, csrf: string, canManage: boolean, draft?: KnowledgeDraft, error?: string): Html;
|
|
8
|
+
export declare function knowledgeHtml(view: KnowledgeView, csrf: string, canManage: boolean, draft?: KnowledgeDraft, error?: string, use?: ReadonlyMap<string, MemoryUse>): Html;
|
|
8
9
|
export declare function knowledgeContextHtml(snapshot: KnowledgeSelection | null): Html | '';
|
|
9
10
|
import type { Decision, MemoryHit } from './project-memory.js';
|
|
10
|
-
export declare const MEMORY_INTRO = "What the lead and crew read before they work. Every change keeps its history.";
|
|
11
|
+
export declare const MEMORY_INTRO = "What the lead and crew read before they work. Every change keeps its history. Last included is counted in runs.";
|
|
11
12
|
export declare function decisionsHtml(repo: string, decisions: Decision[], csrf: string, canManage: boolean, draft?: {
|
|
12
13
|
claim?: string;
|
|
13
14
|
why?: string;
|
|
14
|
-
}, error?: string): Html;
|
|
15
|
+
}, error?: string, use?: ReadonlyMap<string, MemoryUse>, afterwards?: (decision: Decision) => string | null): Html;
|
|
15
16
|
export declare function memorySearchHtml(repo: string, query: string, hits: MemoryHit[] | null): Html;
|
|
16
17
|
import type { MemoryProposal } from './memory-pass.js';
|
|
17
18
|
export declare function proposalsHtml(repo: string, proposals: MemoryProposal[], status: {
|
|
@@ -25,4 +26,20 @@ export declare function proposalsHtml(repo: string, proposals: MemoryProposal[],
|
|
|
25
26
|
};
|
|
26
27
|
openGaps: number;
|
|
27
28
|
pending: number;
|
|
28
|
-
} | null, csrf: string, canManage: boolean): Html;
|
|
29
|
+
} | null, csrf: string, canManage: boolean, who?: string): Html;
|
|
30
|
+
import type { TidyProposal } from './memory-tidy.js';
|
|
31
|
+
export declare function cleanUpHtml(repo: string, proposals: readonly TidyProposal[], manage: boolean): Html;
|
|
32
|
+
/** On a saved version's page: the references that aren't in current knowledge, each back as a new version. */
|
|
33
|
+
export declare function referenceRestoreHtml(repo: string, revision: number, references: readonly {
|
|
34
|
+
id: string;
|
|
35
|
+
title: string;
|
|
36
|
+
}[], manage: boolean): Html | '';
|
|
37
|
+
/** Retired decisions, undoable merges and disabled lessons, each with its way back. */
|
|
38
|
+
export declare function restoreHtml(repo: string, retired: readonly Decision[], merges: readonly {
|
|
39
|
+
merge: number;
|
|
40
|
+
successor: number;
|
|
41
|
+
predecessors: number[];
|
|
42
|
+
}[], lessons: readonly {
|
|
43
|
+
id: number;
|
|
44
|
+
observation: string;
|
|
45
|
+
}[], manage: boolean): Html | '';
|
package/dist/knowledge-ui.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { STALE_KNOWLEDGE } from './project-knowledge.js';
|
|
2
2
|
import { html, joinHtml, postForm } from './html.js';
|
|
3
|
-
import {
|
|
3
|
+
import { lastIncludedWords } from './memory-use.js';
|
|
4
|
+
import { acts, btn, codeLine, disclose, formRow, input, note, notice, radios, settingsLink, settingsRecord, settingsSection, textarea } from './server/kit.js';
|
|
4
5
|
// Hidden fields as they always were: a missing value is an empty field.
|
|
5
6
|
const fields = (data) => Object.fromEntries(Object.entries(data).map(([k, v]) => [k, String(v ?? '')]));
|
|
6
7
|
/** A section as a frame (after ReUI's Frame, MIT): its heading on the raised well, its body one paper panel. The
|
|
@@ -9,7 +10,7 @@ export function framedSection(heading, body, extra = '') {
|
|
|
9
10
|
return html `<section class="${`knowledge frame${extra ? ` ${extra}` : ''}`}"><div class="frame-head"><h2>${heading}</h2></div><div class="frame-panel">${body}</div></section>`;
|
|
10
11
|
}
|
|
11
12
|
export const KNOWLEDGE_CSS = `.knowledge{max-width:780px;min-width:0;overflow-wrap:anywhere}.knowledge .card{padding:20px;min-width:0;margin:16px 0}.knowledge h2{margin:0 0 12px}.knowledge form{display:grid;gap:12px;margin:0}.knowledge label{display:grid;gap:6px}.knowledge textarea,.knowledge input,.knowledge select{box-sizing:border-box;width:100%;min-width:0;max-width:100%}.knowledge textarea{resize:vertical;min-height:110px}.knowledge button,.knowledge summary,.knowledge .button-link{min-height:44px}.knowledge button{justify-self:start;white-space:nowrap}.knowledge summary{padding:12px 0;cursor:pointer}.knowledge pre{white-space:pre-wrap;overflow-wrap:anywhere;font:inherit}.knowledge .knowledge-actions{display:flex;gap:12px;flex-wrap:wrap}.knowledge .meta{font-size:.86rem}.knowledge .knowledge-source{display:block}.knowledge details+details{border-top:1px solid var(--so-line)}.memory-stats{display:flex;flex-wrap:wrap;gap:.4rem}.memory-stats span+span::before{content:"·";margin-right:.4rem}.knowledge.frame{margin:0 0 16px}.knowledge.frame>.frame-head h2,.knowledge.frame>summary.frame-head{margin:0;font-size:13px;font-weight:600;line-height:1.4}.knowledge.frame>summary.frame-head{min-height:44px;padding:4px 10px}.knowledge.frame>.frame-panel{min-width:0}.knowledge.frame>.frame-panel>:first-child{margin-top:0}.knowledge.frame>.frame-panel>:last-child{margin-bottom:0}.knowledge .frame-panel>details.card,.knowledge .frame-panel .learning .card{margin:0;padding:0;border:0;border-radius:0;background:none;box-shadow:none}.knowledge .frame-panel>details.card+details.card,.knowledge .frame-panel .learning .card+.card{border-top:1px solid var(--so-line);padding-top:4px}.knowledge.frame>summary.frame-head{justify-content:flex-start}.knowledge form.field-row{grid-template-columns:minmax(0,1fr) auto;align-items:end}.knowledge form.field-row>label,.knowledge form.field-row :is(input,select,button){margin:0}@media(max-width:600px){.knowledge .card{padding:16px}.knowledge h2{font-size:1.1rem}}`;
|
|
12
|
-
export function knowledgeHtml(view, csrf, canManage, draft, error) {
|
|
13
|
+
export function knowledgeHtml(view, csrf, canManage, draft, error, use = new Map()) {
|
|
13
14
|
const manage = canManage && !!csrf;
|
|
14
15
|
const base = { repo: view.repo, identity: view.identity, revision: view.revision };
|
|
15
16
|
const form = (action, body, extra = {}) => postForm('/settings/knowledge/change', body, { hidden: fields({ ...base, action, ...extra }) });
|
|
@@ -22,6 +23,7 @@ export function knowledgeHtml(view, csrf, canManage, draft, error) {
|
|
|
22
23
|
const refs = view.knowledge.references.map(ref => settingsRecord({
|
|
23
24
|
title: ref.title,
|
|
24
25
|
sub: ref.path ? html `<span class="k-mono">${ref.path} · ${ref.sourceRevision?.slice(0, 7)}</span>` : '',
|
|
26
|
+
when: lastIncludedWords(use.get(`reference:${ref.id}`)),
|
|
25
27
|
more: joinHtml([
|
|
26
28
|
disclose('Read reference', html `<pre class="k-scode">${ref.content}</pre>`, draft?.id === ref.id),
|
|
27
29
|
manage ? disclose('Edit reference', joinHtml([
|
|
@@ -59,14 +61,16 @@ export function knowledgeContextHtml(snapshot) {
|
|
|
59
61
|
return '';
|
|
60
62
|
return html `<details class="knowledge result-knowledge"><summary>Context used</summary><p class="meta">Project knowledge · version ${snapshot.revision}${snapshot.inheritedFrom === null ? '' : ` · from run #${snapshot.inheritedFrom}`}</p>${snapshot.instructions ? html `<details><summary>Instructions</summary><pre>${snapshot.instructions}</pre></details>` : html `<p>No project instructions were supplied.</p>`}${snapshot.references.map(r => html `<details><summary>${r.title}</summary>${r.path ? html `<p class="meta">${r.path} · ${r.sourceRevision}</p>` : ''}<pre>${r.content}</pre></details>`)}${snapshot.omitted.length ? html `<details><summary>Not included (${snapshot.omitted.length})</summary><ul>${snapshot.omitted.map(r => html `<li>${r.title} — ${r.reason}</li>`)}</ul></details>` : ''}</details>`;
|
|
61
63
|
}
|
|
62
|
-
export const MEMORY_INTRO = 'What the lead and crew read before they work. Every change keeps its history.';
|
|
63
|
-
export function decisionsHtml(repo, decisions, csrf, canManage, draft, error) {
|
|
64
|
+
export const MEMORY_INTRO = 'What the lead and crew read before they work. Every change keeps its history. Last included is counted in runs.';
|
|
65
|
+
export function decisionsHtml(repo, decisions, csrf, canManage, draft, error, use = new Map(), afterwards = () => null) {
|
|
64
66
|
const manage = canManage && !!csrf;
|
|
65
67
|
const base = { repo };
|
|
66
68
|
const form = (action, body, extra = {}) => postForm('/settings/knowledge/decision', body, { hidden: fields({ ...base, action, ...extra }) });
|
|
67
69
|
const rows = decisions.map(d => settingsRecord({
|
|
68
70
|
title: d.claim, sub: d.why, when: html `<span class="k-num">#${d.id}</span> · ${d.decidedBy} · ${d.decidedAt.slice(0, 10)}`, attrs: { id: `decision-${d.id}` },
|
|
69
71
|
more: joinHtml([
|
|
72
|
+
note(lastIncludedWords(use.get(`decision:${d.id}`))),
|
|
73
|
+
afterwards(d) === null ? '' : note(afterwards(d)),
|
|
70
74
|
note(`${d.sourceKind}${d.sourceRef ? ` · ${d.sourceRef}` : ''}${d.supersedes === null ? '' : ` · replaces #${d.supersedes}`}`),
|
|
71
75
|
manage ? disclose('Retire', form('retire', joinHtml([
|
|
72
76
|
formRow('Why it no longer holds', null, input({ id: `decision-${d.id}-reason`, name: 'reason', attrs: { maxlength: 500, required: true, 'aria-label': 'Why it no longer holds' } }), 'wide'),
|
|
@@ -89,14 +93,30 @@ export function memorySearchHtml(repo, query, hits) {
|
|
|
89
93
|
results,
|
|
90
94
|
]) });
|
|
91
95
|
}
|
|
92
|
-
|
|
96
|
+
import { NOTE_SURFACE } from './lesson-notes.js';
|
|
97
|
+
export function proposalsHtml(repo, proposals, status, csrf, canManage, who = '') {
|
|
93
98
|
const manage = canManage && !!csrf;
|
|
94
99
|
const line = status === null ? '' : joinHtml([
|
|
95
100
|
note(`${status.sessions.analyzed}/${status.sessions.total} sessions read${status.sessions.failed ? ` · ${status.sessions.failed} failed` : ''} · ${status.openGaps} open gap${status.openGaps === 1 ? '' : 's'} · Instructions ${status.instructionBytes}/${status.budgetBytes} bytes`),
|
|
96
101
|
disclose('Read new sessions', joinHtml([note('Run this on this computer:'), codeLine(`toolroll memory propose --repo ${repo}`)])),
|
|
97
102
|
]);
|
|
98
103
|
const decide = (p, decision, label) => postForm('/settings/knowledge/proposal', btn(label, { sm: true, variant: decision === 'accept' ? 'outline' : 'ghost', type: 'submit' }), { hidden: fields({ repo, proposal: p.id, decision }) });
|
|
99
|
-
|
|
104
|
+
// A lesson note: the facts, the failure line as output, and the person's own words in an edit box that starts as a draft.
|
|
105
|
+
const lessonNote = (p) => settingsRecord({
|
|
106
|
+
title: p.title, sub: html `<span class="k-num">#${p.id}</span> · Lesson note`,
|
|
107
|
+
more: joinHtml([
|
|
108
|
+
note(p.rationale),
|
|
109
|
+
...p.evidence.map(ev => html `<pre class="k-scode">${ev.quote}</pre>`),
|
|
110
|
+
manage ? joinHtml([
|
|
111
|
+
postForm('/settings/knowledge/note', joinHtml([
|
|
112
|
+
formRow('Note', 'Say it in your own words before accepting.', textarea({ id: `note-${p.id}`, name: 'claim', rows: 3, value: p.afterText, attrs: { maxlength: 240, required: true, 'aria-label': 'Note' } }), 'top'),
|
|
113
|
+
acts(btn(`Accept as ${who}`, { sm: true, type: 'submit' })),
|
|
114
|
+
]), { hidden: fields({ repo, proposal: p.id, decision: 'accept' }) }),
|
|
115
|
+
postForm('/settings/knowledge/note', btn('Reject', { sm: true, variant: 'ghost', type: 'submit' }), { hidden: fields({ repo, proposal: p.id, decision: 'reject' }) }),
|
|
116
|
+
]) : '',
|
|
117
|
+
]),
|
|
118
|
+
});
|
|
119
|
+
const cards = proposals.map(p => p.surface === NOTE_SURFACE ? lessonNote(p) : settingsRecord({
|
|
100
120
|
title: p.title, sub: html `<span class="k-num">#${p.id}</span> · ${p.kind} · ${p.sessions} sessions`,
|
|
101
121
|
action: manage ? html `${decide(p, 'accept', 'Accept')}${decide(p, 'reject', 'Reject')}` : '',
|
|
102
122
|
more: joinHtml([
|
|
@@ -106,5 +126,53 @@ export function proposalsHtml(repo, proposals, status, csrf, canManage) {
|
|
|
106
126
|
html `<ul class="k-note">${p.evidence.map(ev => html `<li>“${ev.quote.slice(0, 200)}” · ${ev.session.split(':')[0] ?? ''}</li>`)}</ul>`,
|
|
107
127
|
]),
|
|
108
128
|
}));
|
|
109
|
-
return settingsSection({ id: 'proposals', title: 'Proposed
|
|
129
|
+
return settingsSection({ id: 'proposals', title: 'Proposed changes', body: joinHtml([line, cards.length ? joinHtml(cards) : note('Nothing to review. Suggestions appear when the same mistake shows up in two sessions.')]) });
|
|
130
|
+
}
|
|
131
|
+
const CLEANUP_TITLES = { retire: 'Retire unused', merge: 'Merge duplicates', contradiction: 'Contradiction', 'note-failing': 'Note keeps failing', restore: 'Restore', unmerge: 'Undo merge' };
|
|
132
|
+
const cleanupItem = (p, item) => item.kind === 'decision' ? `Decision #${item.id} · version ${item.revision} · ${item.status}${p.titles[`decision:${item.id}`] ? ` · ${p.titles[`decision:${item.id}`]}` : ''}`
|
|
133
|
+
: item.kind === 'lesson' ? `Lesson #${item.id} · version ${item.version}` : `Reference · knowledge version ${item.revision}`;
|
|
134
|
+
/** Radio choices for a proposal with no default: nothing is pre-selected. */
|
|
135
|
+
const choices = (name, options) => radios({ name, legend: 'Choose one', current: '', items: options.map(([value, label]) => ({ value, label })) });
|
|
136
|
+
export function cleanUpHtml(repo, proposals, manage) {
|
|
137
|
+
const form = (body, hidden) => postForm('/settings/knowledge/tidy', body, { hidden: fields({ repo, ...hidden }) });
|
|
138
|
+
const accept = (p) => {
|
|
139
|
+
const tail = Array.isArray(p.evidence['tail']) ? p.evidence['tail'] : [];
|
|
140
|
+
const decisions = p.items.flatMap(item => item.kind === 'decision' ? [item.id] : []);
|
|
141
|
+
const extra = p.kind === 'contradiction' ? joinHtml([choices('choice', [...decisions.map(id => [`keep:${id}`, `Keep #${id}, retire the other`]), ['both', 'Keep both'], ['new', 'Write a new one']]),
|
|
142
|
+
formRow('New decision', null, input({ id: `tidy-${p.id}-claim`, name: 'claim', attrs: { maxlength: 240, 'aria-label': 'New decision' } }), 'wide'), formRow('Why', null, input({ id: `tidy-${p.id}-why`, name: 'why', attrs: { maxlength: 500, 'aria-label': 'Why' } }), 'wide')])
|
|
143
|
+
: p.kind === 'note-failing' ? joinHtml([choices('choice', [['rewrite', 'Rewrite it'], ['retire', 'Retire it']]),
|
|
144
|
+
formRow('New note', null, input({ id: `tidy-${p.id}-claim`, name: 'claim', attrs: { maxlength: 240, 'aria-label': 'New note' } }), 'wide'), formRow('Why', null, input({ id: `tidy-${p.id}-why`, name: 'why', attrs: { maxlength: 500, 'aria-label': 'Why' } }), 'wide')])
|
|
145
|
+
: (p.kind === 'restore' || p.kind === 'unmerge') && tail.length > 0 ? choices('choice', [['retire', `Retire ${tail.map(id => `#${id}`).join(', ')}`], ['keep', 'Keep both active']])
|
|
146
|
+
: p.kind === 'merge' ? formRow('Merged decision', null, input({ id: `tidy-${p.id}-claim`, name: 'claim', value: p.afterText ?? '', attrs: { maxlength: 240, 'aria-label': 'Merged decision' } }), 'wide') : '';
|
|
147
|
+
return form(joinHtml([extra, acts(btn('Accept', { sm: true, type: 'submit' }))]), { action: 'accept', proposal: p.id });
|
|
148
|
+
};
|
|
149
|
+
const rows = proposals.map(p => settingsRecord({
|
|
150
|
+
title: CLEANUP_TITLES[p.kind], sub: p.rationale,
|
|
151
|
+
more: joinHtml([
|
|
152
|
+
html `<ul class="k-note">${p.items.map(item => html `<li>${cleanupItem(p, item)}</li>`)}</ul>`,
|
|
153
|
+
manage ? joinHtml([accept(p), form(btn('Reject', { sm: true, variant: 'ghost', type: 'submit' }), { action: 'reject', proposal: p.id })]) : '',
|
|
154
|
+
]),
|
|
155
|
+
}));
|
|
156
|
+
return settingsSection({ id: 'clean-up', title: 'Clean up', body: joinHtml([
|
|
157
|
+
rows.length ? joinHtml(rows) : note('Nothing to clean up.'),
|
|
158
|
+
manage ? form(acts(btn('Clean up', { sm: true, type: 'submit' })), { action: 'propose' }) : '',
|
|
159
|
+
]) });
|
|
160
|
+
}
|
|
161
|
+
/** On a saved version's page: the references that aren't in current knowledge, each back as a new version. */
|
|
162
|
+
export function referenceRestoreHtml(repo, revision, references, manage) {
|
|
163
|
+
if (!manage || references.length === 0)
|
|
164
|
+
return '';
|
|
165
|
+
return settingsSection({ id: 'reference-restore', title: 'References no longer in use', body: joinHtml(references.map(ref => settingsRecord({ title: ref.title,
|
|
166
|
+
action: postForm('/settings/knowledge/tidy', btn('Restore', { sm: true, variant: 'ghost', type: 'submit' }), { hidden: fields({ repo, action: 'reference', target: ref.id, revision }) }) }))) });
|
|
167
|
+
}
|
|
168
|
+
/** Retired decisions, undoable merges and disabled lessons, each with its way back. */
|
|
169
|
+
export function restoreHtml(repo, retired, merges, lessons, manage) {
|
|
170
|
+
if (retired.length + merges.length + lessons.length === 0)
|
|
171
|
+
return '';
|
|
172
|
+
const form = (label, action, target) => manage ? postForm('/settings/knowledge/tidy', btn(label, { sm: true, variant: 'ghost', type: 'submit' }), { hidden: fields({ repo, action, target }) }) : '';
|
|
173
|
+
return html `<details class="knowledge frame"><summary class="frame-head">Retired</summary><div class="frame-panel">${joinHtml([
|
|
174
|
+
...merges.map(m => settingsRecord({ title: `Merge of ${m.predecessors.map(id => `#${id}`).join(', ')} into #${m.successor}`, action: form('Undo merge', 'unmerge', m.merge) })),
|
|
175
|
+
...retired.map(d => settingsRecord({ title: d.claim, sub: html `<span class="k-num">#${d.id}</span> · ${d.status}`, action: form('Restore', 'restore', d.id) })),
|
|
176
|
+
...lessons.map(l => settingsRecord({ title: l.observation, sub: 'Lesson', action: form('Use again', 'readopt', l.id) })),
|
|
177
|
+
])}</div></details>`;
|
|
110
178
|
}
|
|
@@ -51,6 +51,8 @@ export type LaptopSeams = {
|
|
|
51
51
|
host?: string;
|
|
52
52
|
healthTimeoutMs?: number;
|
|
53
53
|
exitTimeoutMs?: number;
|
|
54
|
+
/** The build an installed unit would start, from its entry point (default: the version in that package's package.json). */
|
|
55
|
+
buildAt?: (entry: string) => string | null;
|
|
54
56
|
/** The foreground `up` this invoker started (the terminal UI's child): its controller's pid, and how to start it again. */
|
|
55
57
|
foreground?: {
|
|
56
58
|
controllerPid: number;
|
|
@@ -168,4 +170,18 @@ export declare function backgroundInstalled(seams?: LaptopSeams): boolean;
|
|
|
168
170
|
* `up` beside it — and prove the console it runs live. Our own label only.
|
|
169
171
|
*/
|
|
170
172
|
export declare function startInstalledService(store: Store, databaseFile: string, seams?: LaptopSeams): Promise<AttachProof>;
|
|
173
|
+
export type BuilderRestart = {
|
|
174
|
+
restarted: true;
|
|
175
|
+
} | {
|
|
176
|
+
restarted: false;
|
|
177
|
+
reason: "unsupported" | "not-installed" | "not-wanted" | "paused" | "running" | "unproven" | "disabled" | "identity" | "attempts" | "failed";
|
|
178
|
+
message?: string;
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Start our own service again, by Toolroll alone, only when a person's recorded wish is to run, nothing is stopping or
|
|
182
|
+
* updating, the old process is proved gone, what would start is what ran, and it isn't over its attempts. Every other
|
|
183
|
+
* answer leaves the need with one label, "Start the builder". This never enables a disabled label, which is what sets it
|
|
184
|
+
* apart from `startService`: a person's own yes.
|
|
185
|
+
*/
|
|
186
|
+
export declare function restartOwnBuilder(store: Store, databaseFile: string, seams?: LaptopSeams, now?: Date): Promise<BuilderRestart>;
|
|
171
187
|
export {};
|