toolroll 0.9.65 → 0.9.68
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 +93 -0
- package/README.md +20 -2
- package/dist/acp-client.js +1 -3
- package/dist/agent-board.d.ts +0 -1
- package/dist/agent-board.js +0 -1
- package/dist/agent-fence.d.ts +69 -2
- package/dist/agent-fence.js +294 -11
- package/dist/agentconfig.js +4 -1
- package/dist/batch-checks.js +3 -2
- package/dist/browser/THIRD_PARTY_NOTICES.txt +46 -0
- package/dist/browser/terminal.js +134 -23
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +31 -29
- package/dist/builder.d.ts +4 -0
- package/dist/builder.js +56 -37
- package/dist/canonical-json.d.ts +14 -0
- package/dist/canonical-json.js +36 -0
- package/dist/chat-actions.js +1 -2
- package/dist/chat-admission.d.ts +19 -0
- package/dist/chat-admission.js +22 -0
- package/dist/chat-channel.js +1 -1
- package/dist/chat-delivery.js +0 -1
- package/dist/chat-follow.d.ts +17 -0
- package/dist/chat-follow.js +32 -0
- package/dist/chat-messages.js +0 -1
- package/dist/chat-settings-page.d.ts +23 -0
- package/dist/chat-settings-page.js +41 -0
- package/dist/claim.js +3 -6
- package/dist/claude-print.d.ts +12 -0
- package/dist/claude-print.js +34 -0
- package/dist/cli-http.d.ts +0 -7
- package/dist/cli-http.js +0 -21
- package/dist/cli.d.ts +3 -10
- package/dist/cli.js +15 -24
- package/dist/coding-provider.js +2 -1
- package/dist/coding-workspace.js +1 -1
- package/dist/{team-cli.d.ts → connect-cli.d.ts} +5 -9
- package/dist/connect-cli.js +331 -0
- package/dist/containment.d.ts +0 -2
- package/dist/containment.js +2 -6
- package/dist/contracts/chat-actions.d.ts +0 -1
- package/dist/contracts/chat-content.d.ts +0 -4
- package/dist/contracts/chat-content.js +0 -2
- package/dist/contracts/chat-tables.d.ts +0 -1
- package/dist/contracts/cli.d.ts +0 -62
- package/dist/contracts/cli.js +0 -4
- package/dist/contracts/coding-workspace.js +1 -1
- package/dist/contracts/console-api.d.ts +1 -0
- package/dist/contracts/console-api.js +1 -0
- package/dist/contracts/contract.js +1 -1
- package/dist/contracts/flow-card.d.ts +0 -1
- package/dist/contracts/flow-propose.d.ts +0 -1
- package/dist/contracts/flow-send.js +1 -1
- package/dist/contracts/gateway-tools.d.ts +0 -1
- package/dist/contracts/integration-metadata.js +1 -1
- package/dist/contracts/lead-tools.d.ts +0 -1
- package/dist/contracts/memory-pass.d.ts +0 -1
- package/dist/contracts/memory-pass.js +1 -1
- package/dist/contracts/plan.js +1 -1
- package/dist/contracts/project-knowledge.js +1 -1
- package/dist/contracts/project-skills.d.ts +0 -1
- package/dist/contracts/proof.js +1 -1
- package/dist/contracts/recipes.js +1 -1
- package/dist/contracts/scope.js +1 -1
- package/dist/contracts/scout-report.js +1 -1
- package/dist/contracts/teams-callback.d.ts +0 -1
- package/dist/converse.d.ts +0 -6
- package/dist/converse.js +0 -19
- package/dist/daemon.js +3 -2
- package/dist/decision.d.ts +1 -3
- package/dist/deprecations.d.ts +12 -4
- package/dist/deprecations.js +27 -4
- package/dist/desktop-update-recovery.js +1 -1
- package/dist/desktop-update.js +3 -2
- package/dist/discord-chat.js +4 -15
- package/dist/discord-settings.d.ts +2 -6
- package/dist/discord-settings.js +10 -38
- package/dist/discord.js +12 -23
- package/dist/evidence.d.ts +0 -1
- package/dist/evidence.js +0 -3
- package/dist/exec.d.ts +0 -9
- package/dist/exec.js +3 -81
- package/dist/flow-actions.js +0 -3
- package/dist/flow-draft.d.ts +1 -3
- package/dist/flow-draft.js +15 -28
- package/dist/flow-scripts.d.ts +1 -8
- package/dist/flow-scripts.js +0 -13
- package/dist/flow-send.js +1 -1
- package/dist/flow-share.d.ts +0 -2
- package/dist/flow-share.js +1 -6
- package/dist/flow-steps.js +1 -1
- package/dist/flow-triggers.d.ts +0 -1
- package/dist/flow-triggers.js +1 -2
- package/dist/flow-unattended.js +1 -12
- package/dist/flows.js +1 -1
- package/dist/health-cli.d.ts +2 -2
- package/dist/health-cli.js +1 -1
- package/dist/invoke.d.ts +18 -3
- package/dist/invoke.js +142 -17
- package/dist/is-record.d.ts +2 -0
- package/dist/is-record.js +2 -0
- package/dist/lead-doors.d.ts +1 -5
- package/dist/lead-doors.js +0 -4
- package/dist/lead-tools.js +3 -6
- package/dist/live-detail.d.ts +0 -1
- package/dist/live-detail.js +0 -6
- package/dist/live-terminal.d.ts +24 -13
- package/dist/live-terminal.js +81 -43
- package/dist/live.d.ts +0 -1
- package/dist/live.js +1 -2
- package/dist/mcp-core.js +0 -2
- package/dist/memory-pass.js +1 -1
- package/dist/model-catalog.js +1 -1
- package/dist/modes.js +1 -12
- package/dist/operate.d.ts +2 -0
- package/dist/operate.js +436 -745
- package/dist/password-bearer.d.ts +0 -2
- package/dist/password-bearer.js +0 -4
- package/dist/peek-cli.js +2 -1
- package/dist/peek.d.ts +2 -26
- package/dist/peek.js +2 -168
- package/dist/phase-routing.d.ts +0 -4
- package/dist/phase-routing.js +2 -21
- package/dist/planner-source.d.ts +2 -1
- package/dist/planner-source.js +2 -12
- package/dist/planner.js +2 -14
- package/dist/process-tree.d.ts +0 -2
- package/dist/process-tree.js +0 -10
- package/dist/project-delete-ui.d.ts +6 -0
- package/dist/project-delete-ui.js +20 -1
- package/dist/project-delete.d.ts +0 -2
- package/dist/project-delete.js +1 -3
- package/dist/project-learning.d.ts +0 -14
- package/dist/project-skills.d.ts +0 -1
- package/dist/project-skills.js +0 -14
- package/dist/project-tools.js +2 -10
- package/dist/provider.d.ts +0 -17
- package/dist/provider.js +0 -26
- package/dist/pulls.d.ts +0 -2
- package/dist/pulls.js +0 -9
- package/dist/release-adapters.d.ts +9 -0
- package/dist/release-adapters.js +81 -4
- package/dist/release.d.ts +7 -1
- package/dist/release.js +20 -7
- package/dist/remote-exec.d.ts +3 -2
- package/dist/remote-exec.js +3 -2
- package/dist/repos.d.ts +0 -5
- package/dist/repos.js +2 -10
- package/dist/repository-context.d.ts +0 -2
- package/dist/repository-context.js +0 -4
- package/dist/restart-certification.d.ts +0 -2
- package/dist/restart-certification.js +1 -5
- package/dist/result-follow-ups.d.ts +1 -4
- package/dist/result-follow-ups.js +4 -8
- package/dist/result-review.d.ts +0 -2
- package/dist/result-review.js +0 -2
- package/dist/retention.d.ts +0 -1
- package/dist/retention.js +0 -1
- package/dist/review-context.d.ts +0 -1
- package/dist/review-context.js +1 -12
- package/dist/reviewer.d.ts +0 -16
- package/dist/reviewer.js +0 -18
- package/dist/run-closeout.js +2 -1
- package/dist/scope.d.ts +13 -0
- package/dist/scope.js +11 -18
- package/dist/scout-browser.js +2 -1
- package/dist/serve.js +3 -2
- package/dist/server/chrome.d.ts +10 -1
- package/dist/server/chrome.js +45 -4
- package/dist/server/console.css +60 -137
- package/dist/server/flows.js +2 -2
- package/dist/server/http.d.ts +2 -0
- package/dist/server/http.js +5 -0
- package/dist/server/pages.js +2 -2
- package/dist/server/people-tokens.js +3 -4
- package/dist/server/remote.js +1 -6
- package/dist/server/render-chat.d.ts +9 -2
- package/dist/server/render-chat.js +53 -7
- package/dist/server/render-pages.d.ts +28 -3
- package/dist/server/render-pages.js +78 -9
- package/dist/server/render-results.d.ts +0 -6
- package/dist/server/render-results.js +3 -9
- package/dist/server/render-tasks.js +6 -6
- package/dist/server/route-table.d.ts +0 -4
- package/dist/server/route-table.js +1 -6
- package/dist/server/settings.js +43 -22
- package/dist/server/task-views.js +1 -1
- package/dist/server/tasks.js +10 -7
- package/dist/setup-guide.d.ts +2 -2
- package/dist/skills.d.ts +0 -6
- package/dist/skills.js +0 -8
- package/dist/slack-chat.d.ts +1 -7
- package/dist/slack-chat.js +5 -18
- package/dist/slack-settings.d.ts +2 -6
- package/dist/slack-settings.js +10 -38
- package/dist/slack.js +12 -23
- package/dist/sleep.d.ts +2 -0
- package/dist/sleep.js +2 -0
- package/dist/store.d.ts +11 -74
- package/dist/store.js +36 -179
- package/dist/structured-output.d.ts +0 -1
- package/dist/structured-output.js +0 -1
- package/dist/subagent-work.d.ts +0 -2
- package/dist/subagent-work.js +0 -2
- package/dist/subagents.d.ts +1 -3
- package/dist/subagents.js +20 -33
- package/dist/subscription-chat.d.ts +1 -3
- package/dist/subscription-chat.js +2 -3
- package/dist/task-sizing.d.ts +1 -4
- package/dist/task-sizing.js +3 -10
- package/dist/teams-chat.js +4 -11
- package/dist/teams.js +12 -19
- package/dist/teardown-git.d.ts +22 -0
- package/dist/teardown-git.js +136 -2
- package/dist/telegram-lead.js +2 -2
- package/dist/telegram.js +1 -1
- package/dist/token-notices.d.ts +0 -11
- package/dist/token-notices.js +1 -5
- package/dist/tool-broker.d.ts +12 -0
- package/dist/tool-broker.js +66 -2
- package/dist/tool-secret-store.d.ts +0 -3
- package/dist/tool-secret-store.js +2 -11
- package/dist/toolroll-update.d.ts +0 -2
- package/dist/toolroll-update.js +2 -2
- package/dist/unfenced-test-support.d.ts +2 -0
- package/dist/unfenced-test-support.js +11 -0
- package/dist/verification-evidence.d.ts +0 -1
- package/dist/verification-evidence.js +0 -1
- package/dist/webhooks.d.ts +0 -1
- package/dist/webhooks.js +0 -1
- package/dist/workspace-ui.d.ts +0 -8
- package/dist/workspace-ui.js +1 -6
- package/dist/writes-mode.d.ts +9 -0
- package/dist/writes-mode.js +8 -0
- package/package.json +5 -1
- package/dist/contracts/native-census.d.ts +0 -42
- package/dist/contracts/native-census.js +0 -36
- package/dist/process-recovery-anchor.d.ts +0 -75
- package/dist/process-recovery-anchor.js +0 -188
- package/dist/process-recovery-coalition.d.ts +0 -25
- package/dist/process-recovery-coalition.js +0 -105
- package/dist/process-recovery-eligibility.d.ts +0 -179
- package/dist/process-recovery-eligibility.js +0 -174
- package/dist/process-recovery-native.d.ts +0 -161
- package/dist/process-recovery-native.js +0 -595
- package/dist/process-recovery-provenance.d.ts +0 -163
- package/dist/process-recovery-provenance.js +0 -292
- package/dist/process-recovery-services.d.ts +0 -56
- package/dist/process-recovery-services.js +0 -191
- package/dist/process-recovery-settlement.d.ts +0 -32
- package/dist/process-recovery-settlement.js +0 -133
- package/dist/process-recovery.d.ts +0 -41
- package/dist/process-recovery.js +0 -91
- package/dist/session-brief.d.ts +0 -8
- package/dist/session-brief.js +0 -24
- package/dist/session-cli.d.ts +0 -42
- package/dist/session-cli.js +0 -271
- package/dist/session-contract.d.ts +0 -183
- package/dist/session-contract.js +0 -122
- package/dist/session-remote.d.ts +0 -19
- package/dist/session-remote.js +0 -86
- package/dist/session-service.d.ts +0 -22
- package/dist/session-service.js +0 -157
- package/dist/team-cli.js +0 -692
package/dist/builder.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WritesMode } from "./writes-mode.js";
|
|
1
2
|
import type { LaunchNetwork } from "./invoke.js";
|
|
2
3
|
import type { ProviderId } from "./contracts/provider.js";
|
|
3
4
|
import { type PreparedEvidence } from "./prepared-evidence.js";
|
|
@@ -186,6 +187,9 @@ export declare function redactSecretAssignments(text: string): string;
|
|
|
186
187
|
/** A build's network (egress.ts): the one its approval sealed, or — sealed before network policies existed — the
|
|
187
188
|
* narrowed legacy one (provider hosts and the repository's registries, nothing added since). */
|
|
188
189
|
export declare function launchNetworkOf(store: Store, taskRef: number, profile: ExecutionProfile): LaunchNetwork;
|
|
190
|
+
/** Where a build may write (writes-mode.ts): what its approval sealed, or — sealed before write allowlists — anywhere
|
|
191
|
+
* its fence allows, as it was approved. */
|
|
192
|
+
export declare function launchWritesOf(profile: ExecutionProfile): WritesMode;
|
|
189
193
|
export declare function proveApprovedProfile(scope: Scope | null, given: {
|
|
190
194
|
provider: string;
|
|
191
195
|
model: string | undefined;
|
package/dist/builder.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { legacyNetwork, repositoryEcosystems } from "./egress.js";
|
|
2
|
+
import { checkoutGit } from "./teardown-git.js";
|
|
2
3
|
import { isAcpProvider } from "./acp-agents.js";
|
|
3
4
|
import { OBSERVATION_MAILBOX, observationBrief, parseObservationCases, collectObservations } from "./observations.js";
|
|
4
5
|
import { checkCommandFor, effectiveCheckLevel, recordRunCheckLevel } from "./check-levels.js";
|
|
@@ -41,9 +42,9 @@ import { PREPARED_EVIDENCE_FILE, PREPARED_EVIDENCE_GIT, preparedScreenshotMatche
|
|
|
41
42
|
* watchdog and a high runaway-turn breaker still stop a pathological loop;
|
|
42
43
|
* repair remains narrowly time-bounded because its job is narrowly scoped.
|
|
43
44
|
*/
|
|
44
|
-
import { existsSync, unlinkSync, writeFileSync } from "node:fs";
|
|
45
|
+
import { existsSync, realpathSync, unlinkSync, writeFileSync } from "node:fs";
|
|
45
46
|
import { homedir } from "node:os";
|
|
46
|
-
import { join } from "node:path";
|
|
47
|
+
import { join, resolve } from "node:path";
|
|
47
48
|
import { run, runClaudeStreamJsonl, runOwnerTag, streamsInput } from "./exec.js";
|
|
48
49
|
import { stopRequestedFor, stopWords, underStopWatch } from "./task-control.js";
|
|
49
50
|
import { witnessedRunner } from "./process-custody.js";
|
|
@@ -58,7 +59,7 @@ import { heartbeat as runnerHeartbeat } from "./runner.js";
|
|
|
58
59
|
import { MARKER as LEASE_MARKER } from "./worktree.js";
|
|
59
60
|
import { parseDecision, parseHandoff, repairPrompt, HEADLESS_RULE, HANDOFF_CONCLUSION_CAP, HANDOFF_ITEM_CAP, HANDOFF_LIST_CAP, HANDOFF_PAYLOAD_CAP } from "./decision.js";
|
|
60
61
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
61
|
-
import { invokeAgent } from "./invoke.js";
|
|
62
|
+
import { fencedCommands, invokeAgent } from "./invoke.js";
|
|
62
63
|
import { TELEGRAM_TOKEN_ENVS } from "./names.js";
|
|
63
64
|
import { OPENROUTER_ENV_KEY, auditOf, ALL_CREDENTIAL_ENV } from "./provider.js";
|
|
64
65
|
import { liveLogContext } from "./live-detail.js";
|
|
@@ -73,7 +74,7 @@ import { captureParkEvidence, captureTerminalDiff, captureBaseTree, evidenceRoot
|
|
|
73
74
|
import { attemptKeyOf, closeRunQuestions, noteOpenTool, recordAgentTurn } from "./agent-board.js";
|
|
74
75
|
import { openStatusFile, pulseAgentBoard, removeStatusFile, statusBrief } from "./agent-pulse.js";
|
|
75
76
|
import { withAgentTurns } from "./agent-turns.js";
|
|
76
|
-
import { PROOF_LIMITS, parseProof, serializeProof, adjudicate,
|
|
77
|
+
import { PROOF_LIMITS, parseProof, serializeProof, adjudicate, sameDiffStatFacts } from "./proof.js";
|
|
77
78
|
import { parseHandoffArtifact } from "./contracts/handoff.js";
|
|
78
79
|
import { captureReviewContext } from "./review-context.js";
|
|
79
80
|
import { authoritySnapshotDigest, classifyRevisionAuthority, isMilestoneRegression, milestonesOf, parseExecutionPlanDocument, parsePlanRevisionProposal, parseProgressSnapshot, renderExecutionPlanDocument, PROGRESS_LIMITS, REVISION_LIMITS, } from "./plan.js";
|
|
@@ -237,6 +238,43 @@ export function launchNetworkOf(store, taskRef, profile) {
|
|
|
237
238
|
const repo = store.refForId(taskRef)?.repo ?? null;
|
|
238
239
|
return { policy: legacyNetwork(profile.provider, repo === null ? [] : repositoryEcosystems(repo)) };
|
|
239
240
|
}
|
|
241
|
+
/** Asking a checkout which branch it is on: reads no file a build wrote and runs nothing. */
|
|
242
|
+
const BRANCH_QUERIES = [
|
|
243
|
+
["--no-optional-locks", "rev-parse", "--abbrev-ref", "HEAD"], ["--no-optional-locks", "symbolic-ref", "--short", "-q", "HEAD"],
|
|
244
|
+
];
|
|
245
|
+
/**
|
|
246
|
+
* The plane's git as a build runs it: hardened (teardown-git.ts checkoutGit) for every call, failing closed, but one —
|
|
247
|
+
* asking the project's own main checkout which branch it is on (its config is the operator's, and the query runs
|
|
248
|
+
* nothing). An injected runner (`request.git`: tests only; production always runs git itself) is used as given.
|
|
249
|
+
*/
|
|
250
|
+
function buildCheckoutGit(given, worktree, mainCheckout = null) {
|
|
251
|
+
if (given !== run)
|
|
252
|
+
return given;
|
|
253
|
+
const hardened = checkoutGit(given);
|
|
254
|
+
const real = (path) => { try {
|
|
255
|
+
return realpathSync(resolve(path));
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
return null;
|
|
259
|
+
} };
|
|
260
|
+
const trusted = mainCheckout === null ? null : real(mainCheckout);
|
|
261
|
+
const own = real(worktree);
|
|
262
|
+
return (file, args, options) => {
|
|
263
|
+
const at = options?.cwd === undefined ? null : real(options.cwd);
|
|
264
|
+
const query = BRANCH_QUERIES.some(one => one.length === args.length && one.every((word, index) => args[index] === word));
|
|
265
|
+
return trusted !== null && at === trusted && at !== own && query ? given(file, args, options) : hardened(file, args, options);
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/** A run's setup and check commands, in its build's fence, writing where the approval this build runs under sealed
|
|
269
|
+
* (never the scope as it may since have been rewritten; invoke.ts fencedCommands). */
|
|
270
|
+
function commandRunner(store, runId, profile, inner) {
|
|
271
|
+
return fencedCommands(store, runId, launchWritesOf(profile), inner);
|
|
272
|
+
}
|
|
273
|
+
/** Where a build may write (writes-mode.ts): what its approval sealed, or — sealed before write allowlists — anywhere
|
|
274
|
+
* its fence allows, as it was approved. */
|
|
275
|
+
export function launchWritesOf(profile) {
|
|
276
|
+
return profile.writes ?? "anywhere";
|
|
277
|
+
}
|
|
240
278
|
export function proveApprovedProfile(scope, given) {
|
|
241
279
|
// The raw terms first (raw authority repair): a scope whose stored terms
|
|
242
280
|
// do not read back exactly proves nothing — not the filtered reading.
|
|
@@ -331,7 +369,9 @@ export function proveApprovedProfile(scope, given) {
|
|
|
331
369
|
* that forgot one is exactly the caller this is protecting against.
|
|
332
370
|
*/
|
|
333
371
|
export async function build(store, request) {
|
|
334
|
-
const { taskId, taskRef, runner, worktree, branch, now, permissionMode = "auto", skipPermissions = false, provider = "claude",
|
|
372
|
+
const { taskId, taskRef, runner, worktree, branch, now, permissionMode = "auto", skipPermissions = false, provider = "claude", maxTurns = DEFAULT_MAX_TURNS, timeoutMs = DEFAULT_BUILD_TIMEOUT_MS, agent, git: givenGit = run, } = request;
|
|
373
|
+
// The plane's own git on this build's checkout runs hardened, and never where the checkout's config names a command.
|
|
374
|
+
const git = buildCheckoutGit(givenGit, worktree, store.getWorktree(worktree)?.repo ?? null);
|
|
335
375
|
const scope = store.getScope(taskId);
|
|
336
376
|
const approval = approvalOf(scope);
|
|
337
377
|
if (!approval.approved) {
|
|
@@ -625,7 +665,7 @@ export async function build(store, request) {
|
|
|
625
665
|
// Setup runs under the stop watch (v52), owned by this run: an
|
|
626
666
|
// operator's stop ends the setup's process group and the attempt
|
|
627
667
|
// settles as interrupted below, never as a setup failure.
|
|
628
|
-
const made = await underStopWatch(store, request.runId, () => runWithIsolatedDatabase(witnessedRunner(store, request.runId, request.clock ?? (() => now), runSetup), shell.file, shell.args, {
|
|
668
|
+
const made = await underStopWatch(store, request.runId, () => runWithIsolatedDatabase(witnessedRunner(store, request.runId, request.clock ?? (() => now), commandRunner(store, request.runId, effective.profile, runSetup)), shell.file, shell.args, {
|
|
629
669
|
cwd: worktree,
|
|
630
670
|
timeoutMs: setupWanted.timeoutMs,
|
|
631
671
|
processGroup: true,
|
|
@@ -1310,6 +1350,7 @@ export async function build(store, request) {
|
|
|
1310
1350
|
...watchdogsOf(effective.profile, effective.timeoutMs),
|
|
1311
1351
|
omitEnv: AGENT_ENV_DENYLIST,
|
|
1312
1352
|
network: launchNetworkOf(store, request.taskRef, effective.profile),
|
|
1353
|
+
writes: launchWritesOf(effective.profile),
|
|
1313
1354
|
...(agent === undefined ? {} : { runner: agent }),
|
|
1314
1355
|
onSpawn: (pid) => { spawned = true; request.onProviderSpawn?.(pid); },
|
|
1315
1356
|
onStreamEvent: (event) => liveLog.observe(event),
|
|
@@ -1486,7 +1527,7 @@ export async function settleProviderOutcome(captured, result) {
|
|
|
1486
1527
|
}
|
|
1487
1528
|
catch { /* The commit gate still excludes it. */ }
|
|
1488
1529
|
}
|
|
1489
|
-
const { store, request, agent, git, worktree, branch, baseRevision, taskId, taskRef, runner, provider, scope, effective, answers, timeoutMs, root, mailbox, done,
|
|
1530
|
+
const { store, request, agent, git, worktree, branch, baseRevision, taskId, taskRef, runner, provider, scope, effective, answers, timeoutMs, root, mailbox, done, clock } = captured;
|
|
1490
1531
|
// THE STOP FENCE after the provider (v52): a stop recorded while the
|
|
1491
1532
|
// agent ran ends the attempt HERE, before any handoff is read, any park
|
|
1492
1533
|
// sealed, or any commit made — whatever the process wrote is preserved
|
|
@@ -1774,7 +1815,7 @@ export async function settleProviderOutcome(captured, result) {
|
|
|
1774
1815
|
};
|
|
1775
1816
|
if (!eligible())
|
|
1776
1817
|
throw Error("The observation source or execution authority changed.");
|
|
1777
|
-
const execute = (file, args, options) => underStopWatch(store, request.runId, () => runWithIsolatedDatabase(witnessedRunner(store, request.runId, clock, request.verify ?? run), file, args, {
|
|
1818
|
+
const execute = (file, args, options) => underStopWatch(store, request.runId, () => runWithIsolatedDatabase(witnessedRunner(store, request.runId, clock, commandRunner(store, request.runId, captured.effective.profile, request.verify ?? run)), file, args, {
|
|
1778
1819
|
...options, processGroup: true, owner: runOwnerTag(store, request.runId), beforeSpawn: eligible,
|
|
1779
1820
|
onSpawn: pid => request.onProviderSpawn?.(pid), envAllowlist: SETUP_ENV_ALLOWLIST, omitEnv: SETUP_ENV_DENYLIST,
|
|
1780
1821
|
}));
|
|
@@ -2234,7 +2275,7 @@ async function settleProof(captured, statArtifactId, sealedHead) {
|
|
|
2234
2275
|
const runVerification = async (label) => {
|
|
2235
2276
|
const streamed = { stdout: false, stderr: false };
|
|
2236
2277
|
// The check runs under the stop watch (v52), owned by this run.
|
|
2237
|
-
const result = await underStopWatch(store, runId, () => runWithIsolatedDatabase(witnessedRunner(store, runId, now, verifyRunner), verifyShell.file, verifyShell.args, {
|
|
2278
|
+
const result = await underStopWatch(store, runId, () => runWithIsolatedDatabase(witnessedRunner(store, runId, now, commandRunner(store, runId, captured.effective.profile, verifyRunner)), verifyShell.file, verifyShell.args, {
|
|
2238
2279
|
cwd: worktree,
|
|
2239
2280
|
timeoutMs: configured.timeoutMs,
|
|
2240
2281
|
processGroup: true,
|
|
@@ -2276,7 +2317,7 @@ async function settleProof(captured, statArtifactId, sealedHead) {
|
|
|
2276
2317
|
detachShared(worktree, depsRoot);
|
|
2277
2318
|
const label = "Own install · approved project setup (package.json or lockfile changed)";
|
|
2278
2319
|
const setupShell = approvedCommandShell(live.command);
|
|
2279
|
-
const installed = await underStopWatch(store, runId, () => runWithIsolatedDatabase(witnessedRunner(store, runId, now, request.setup ?? run), setupShell.file, setupShell.args, {
|
|
2320
|
+
const installed = await underStopWatch(store, runId, () => runWithIsolatedDatabase(witnessedRunner(store, runId, now, commandRunner(store, runId, captured.effective.profile, request.setup ?? run)), setupShell.file, setupShell.args, {
|
|
2280
2321
|
cwd: worktree,
|
|
2281
2322
|
timeoutMs: live.timeoutMs,
|
|
2282
2323
|
processGroup: true,
|
|
@@ -2295,7 +2336,7 @@ async function settleProof(captured, statArtifactId, sealedHead) {
|
|
|
2295
2336
|
checkLog.push(attemptLog(label, live.command, installed));
|
|
2296
2337
|
if (installed.notFound || installed.timedOut || installed.code !== 0)
|
|
2297
2338
|
return "own-install-failed";
|
|
2298
|
-
return await sealedTreeState(request.git ?? run) === "clean" ? null : "setup-changed-files";
|
|
2339
|
+
return await sealedTreeState(buildCheckoutGit(request.git ?? run, worktree)) === "clean" ? null : "setup-changed-files";
|
|
2299
2340
|
};
|
|
2300
2341
|
const ownInstall = await ownInstallIfChanged();
|
|
2301
2342
|
const first = ownInstall === null ? await runVerification("Project check · attempt 1") : null;
|
|
@@ -2339,7 +2380,7 @@ async function settleProof(captured, statArtifactId, sealedHead) {
|
|
|
2339
2380
|
verifyCommand = { configured: true, ran: false, attemptFailed: true, failure: "custody-lost" };
|
|
2340
2381
|
}
|
|
2341
2382
|
else {
|
|
2342
|
-
const gitRunner = request.git ?? run;
|
|
2383
|
+
const gitRunner = buildCheckoutGit(request.git ?? run, worktree);
|
|
2343
2384
|
const beforeSetup = await sealedTreeState(gitRunner);
|
|
2344
2385
|
if (beforeSetup === "head-moved") {
|
|
2345
2386
|
verifyCommand = { configured: true, ran: false, attemptFailed: true, failure: "checkout-moved" };
|
|
@@ -2366,7 +2407,7 @@ async function settleProof(captured, statArtifactId, sealedHead) {
|
|
|
2366
2407
|
else {
|
|
2367
2408
|
const setupRunner = request.setup ?? run;
|
|
2368
2409
|
const setupShell = approvedCommandShell(liveBeforeSetup.command);
|
|
2369
|
-
const restored = await underStopWatch(store, runId, () => runWithIsolatedDatabase(witnessedRunner(store, runId, now, setupRunner), setupShell.file, setupShell.args, {
|
|
2410
|
+
const restored = await underStopWatch(store, runId, () => runWithIsolatedDatabase(witnessedRunner(store, runId, now, commandRunner(store, runId, captured.effective.profile, setupRunner)), setupShell.file, setupShell.args, {
|
|
2370
2411
|
cwd: worktree,
|
|
2371
2412
|
timeoutMs: liveBeforeSetup.timeoutMs,
|
|
2372
2413
|
processGroup: true,
|
|
@@ -2653,6 +2694,7 @@ async function resumeUnhandedWork(captured, sessionId) {
|
|
|
2653
2694
|
...watchdogsOf(effective.profile, effective.timeoutMs),
|
|
2654
2695
|
omitEnv: AGENT_ENV_DENYLIST,
|
|
2655
2696
|
network: launchNetworkOf(store, request.taskRef, effective.profile),
|
|
2697
|
+
writes: launchWritesOf(effective.profile),
|
|
2656
2698
|
...(agent === undefined ? {} : { runner: agent }),
|
|
2657
2699
|
...(request.onProviderSpawn === undefined ? {} : { onSpawn: request.onProviderSpawn }),
|
|
2658
2700
|
clock,
|
|
@@ -2867,7 +2909,7 @@ async function ingestPark(args) {
|
|
|
2867
2909
|
? watchdogsOf(args.profile, args.profile.repairTimeoutSeconds * 1000, true)
|
|
2868
2910
|
: { timeoutMs: args.profile !== undefined ? args.profile.repairTimeoutSeconds * 1000 : REPAIR_TIMEOUT_MS }),
|
|
2869
2911
|
omitEnv: AGENT_ENV_DENYLIST,
|
|
2870
|
-
...(args.profile === undefined ? {} : { network: launchNetworkOf(store, request.taskRef, args.profile) }),
|
|
2912
|
+
...(args.profile === undefined ? {} : { network: launchNetworkOf(store, request.taskRef, args.profile), writes: launchWritesOf(args.profile) }),
|
|
2871
2913
|
...(agent === undefined ? {} : { runner: agent }),
|
|
2872
2914
|
...(request.onProviderSpawn === undefined ? {} : { onSpawn: request.onProviderSpawn }),
|
|
2873
2915
|
clock,
|
|
@@ -3492,29 +3534,6 @@ async function stripEvidenceImages(git, worktree) {
|
|
|
3492
3534
|
const rest = await git(GIT, ["--no-optional-locks", "diff", "--cached", "--quiet"], { cwd: worktree });
|
|
3493
3535
|
return { ok: true, notice: evidenceImagesNotice(images), staged: rest.code !== 0 };
|
|
3494
3536
|
}
|
|
3495
|
-
/**
|
|
3496
|
-
* `claude --output-format json` returns an envelope: the result is the
|
|
3497
|
-
* summary, and the session id is what lets a malformed park be repaired by
|
|
3498
|
-
* resuming the conversation that produced it instead of paying for a new one.
|
|
3499
|
-
*/
|
|
3500
|
-
function envelope(stdout) {
|
|
3501
|
-
try {
|
|
3502
|
-
const parsed = JSON.parse(stdout);
|
|
3503
|
-
return {
|
|
3504
|
-
summary: typeof parsed.result === "string" && parsed.result.trim() !== ""
|
|
3505
|
-
? parsed.result.trim()
|
|
3506
|
-
: "unattended build",
|
|
3507
|
-
...(typeof parsed.session_id === "string" && parsed.session_id !== ""
|
|
3508
|
-
? { sessionId: parsed.session_id }
|
|
3509
|
-
: {}),
|
|
3510
|
-
};
|
|
3511
|
-
}
|
|
3512
|
-
catch {
|
|
3513
|
-
// An agent that printed something unparseable still did work — and a
|
|
3514
|
-
// session nobody can name simply cannot be resumed.
|
|
3515
|
-
return { summary: "unattended build" };
|
|
3516
|
-
}
|
|
3517
|
-
}
|
|
3518
3537
|
/**
|
|
3519
3538
|
* What a non-zero agent exit means, in words a person can act on: the
|
|
3520
3539
|
* harness's own result line names the ending (a turn ceiling, an error
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic JSON for digests and equality. These bytes are sealed into approvals and stored digests, so neither
|
|
3
|
+
* function may change its output for any input. The two differ on purpose and must stay distinct:
|
|
4
|
+
*
|
|
5
|
+
* - `canonicalJson` drops object keys whose value is undefined and returns JSON.stringify's own answer for a bare
|
|
6
|
+
* value (an undefined array element therefore renders as nothing: `[,1]`).
|
|
7
|
+
* - `canonicalJsonUndefinedAsNull` keeps every key and renders an undefined (or otherwise unserializable) value as
|
|
8
|
+
* `null` — the planner contract's bytes.
|
|
9
|
+
*
|
|
10
|
+
* Both sort object keys by UTF-16 code units and keep arrays in order.
|
|
11
|
+
*/
|
|
12
|
+
export declare function canonicalJson(value: unknown): string;
|
|
13
|
+
/** Stable JSON for hashing and equality only — no filling, no trimming; undefined reads as null. */
|
|
14
|
+
export declare function canonicalJsonUndefinedAsNull(value: unknown): string;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deterministic JSON for digests and equality. These bytes are sealed into approvals and stored digests, so neither
|
|
3
|
+
* function may change its output for any input. The two differ on purpose and must stay distinct:
|
|
4
|
+
*
|
|
5
|
+
* - `canonicalJson` drops object keys whose value is undefined and returns JSON.stringify's own answer for a bare
|
|
6
|
+
* value (an undefined array element therefore renders as nothing: `[,1]`).
|
|
7
|
+
* - `canonicalJsonUndefinedAsNull` keeps every key and renders an undefined (or otherwise unserializable) value as
|
|
8
|
+
* `null` — the planner contract's bytes.
|
|
9
|
+
*
|
|
10
|
+
* Both sort object keys by UTF-16 code units and keep arrays in order.
|
|
11
|
+
*/
|
|
12
|
+
export function canonicalJson(value) {
|
|
13
|
+
if (Array.isArray(value))
|
|
14
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
15
|
+
if (value !== null && typeof value === "object") {
|
|
16
|
+
const entries = Object.entries(value)
|
|
17
|
+
.filter(([, v]) => v !== undefined)
|
|
18
|
+
.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
|
|
19
|
+
.map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`);
|
|
20
|
+
return `{${entries.join(",")}}`;
|
|
21
|
+
}
|
|
22
|
+
return JSON.stringify(value);
|
|
23
|
+
}
|
|
24
|
+
/** Stable JSON for hashing and equality only — no filling, no trimming; undefined reads as null. */
|
|
25
|
+
export function canonicalJsonUndefinedAsNull(value) {
|
|
26
|
+
if (Array.isArray(value))
|
|
27
|
+
return `[${value.map(canonicalJsonUndefinedAsNull).join(",")}]`;
|
|
28
|
+
if (value !== null && typeof value === "object") {
|
|
29
|
+
const body = value;
|
|
30
|
+
return `{${Object.keys(body)
|
|
31
|
+
.sort()
|
|
32
|
+
.map(key => `${JSON.stringify(key)}:${canonicalJsonUndefinedAsNull(body[key])}`)
|
|
33
|
+
.join(",")}}`;
|
|
34
|
+
}
|
|
35
|
+
return JSON.stringify(value) ?? "null";
|
|
36
|
+
}
|
package/dist/chat-actions.js
CHANGED
|
@@ -16,7 +16,6 @@ import { changeKnowledge, knowledgeVersion, knowledgeView, } from "./project-kno
|
|
|
16
16
|
import { passwordlessSessionFor, approve, authenticateApprover, describeScope, scopeAuthorityOf, freshnessFor, spendingOnlyOnSuccess, ssoNowAvailable, withFreshness, } from "./scope.js";
|
|
17
17
|
import { readVerifiedArtifact, scanForSecrets } from "./evidence.js";
|
|
18
18
|
import { readAuthModeStrict } from "./keys.js";
|
|
19
|
-
import { parseProof } from "./proof.js";
|
|
20
19
|
import { acceptAndCompleteAsOperator, assignmentOf } from "./assignment.js";
|
|
21
20
|
import { getDecision, recordDecision, retireDecision } from "./project-memory.js";
|
|
22
21
|
import { resumeTaskStop, taskControlOf } from "./task-control.js";
|
|
@@ -26,7 +25,7 @@ import { saveScript, scriptDigest, validateScript } from "./flow-scripts.js";
|
|
|
26
25
|
import { addCardToFlow, advanceFlows, cancelFlowCard, crossProjectProblem, decideFlowCard, flowCardHref, flowCardText, flowDefinitionOf, moveCardInFlow } from "./flow-engine.js";
|
|
27
26
|
import { chooseFlowCard, flowPersonOf } from "./flow-send.js";
|
|
28
27
|
import { assignFlowCard, commentOnFlowCard, flowPeople, mentionsIn, watchFlowCard } from "./flow-people.js";
|
|
29
|
-
import { addFlowTriggerTo, describeTrigger, readLinearKey, removeFlowTrigger,
|
|
28
|
+
import { addFlowTriggerTo, describeTrigger, readLinearKey, removeFlowTrigger, takesDeliveries, triggerConfigOf, validateTriggerConfig } from "./flow-triggers.js";
|
|
30
29
|
import { describeSchedule, parseSchedule } from "./flow-schedule.js";
|
|
31
30
|
import { dirname } from "node:path";
|
|
32
31
|
import { handleOf, parseSoul, SOUL_CHARS, SUBAGENT_TEMPLATES, subagentLabel } from "./subagents.js";
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { ChatBinding, ChatState } from "./chat-delivery-state.js";
|
|
2
|
+
import type { ChatEventBody } from "./contracts/chat-content.js";
|
|
3
|
+
import type { ChatApp } from "./contracts/flow.js";
|
|
4
|
+
/**
|
|
5
|
+
* Whether a received message or tap, already proved to come from this app, may be queued — and under which binding.
|
|
6
|
+
* A room (any conversation that isn't a direct message) is taken only while it follows a conversation, for the words
|
|
7
|
+
* that make it follow one, or when it feeds a flow (v89: anyone's message then, as a card with no say over anything).
|
|
8
|
+
* A pairing message comes only from someone unpaired, in a direct message; everything else only from a live paired
|
|
9
|
+
* member in their own conversation or a followed room. Null when it is not taken.
|
|
10
|
+
*/
|
|
11
|
+
export declare function admitChatEvent(state: ChatState, app: ChatApp, installation: string, event: {
|
|
12
|
+
kind: "message" | "pair" | "action";
|
|
13
|
+
member: string;
|
|
14
|
+
channel: string;
|
|
15
|
+
isRoom: boolean;
|
|
16
|
+
payload: ChatEventBody;
|
|
17
|
+
}): {
|
|
18
|
+
binding: ChatBinding | null;
|
|
19
|
+
} | null;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { channelInbox } from "./chat-inbox.js";
|
|
2
|
+
import { roomCommand } from "./chat-rooms.js";
|
|
3
|
+
/**
|
|
4
|
+
* Whether a received message or tap, already proved to come from this app, may be queued — and under which binding.
|
|
5
|
+
* A room (any conversation that isn't a direct message) is taken only while it follows a conversation, for the words
|
|
6
|
+
* that make it follow one, or when it feeds a flow (v89: anyone's message then, as a card with no say over anything).
|
|
7
|
+
* A pairing message comes only from someone unpaired, in a direct message; everything else only from a live paired
|
|
8
|
+
* member in their own conversation or a followed room. Null when it is not taken.
|
|
9
|
+
*/
|
|
10
|
+
export function admitChatEvent(state, app, installation, event) {
|
|
11
|
+
const { kind, member, channel, isRoom, payload } = event;
|
|
12
|
+
const words = "text" in payload ? payload.text ?? "" : "";
|
|
13
|
+
const inbox = isRoom && kind === "message" ? channelInbox(state.store, app, installation, channel, words) : { watched: false, command: false };
|
|
14
|
+
const roomish = isRoom && (state.room(installation, channel) !== null || (kind === "message" && roomCommand(words) !== null) || inbox.watched || inbox.command);
|
|
15
|
+
if (isRoom && !roomish)
|
|
16
|
+
return null;
|
|
17
|
+
const binding = state.bindingFor(installation, member);
|
|
18
|
+
const open = inbox.watched && !inbox.command;
|
|
19
|
+
if (kind === "pair" ? !!binding || isRoom : !open && (!binding || !state.live(binding) || (binding.channel !== channel && !roomish)))
|
|
20
|
+
return null;
|
|
21
|
+
return { binding };
|
|
22
|
+
}
|
package/dist/chat-channel.js
CHANGED
|
@@ -9,7 +9,7 @@ import { LEAD_TOOL_SCHEMAS } from "./lead-tools.js";
|
|
|
9
9
|
import { verifyApproverStanding } from "./principal.js";
|
|
10
10
|
import { canonicalProject } from "./project.js";
|
|
11
11
|
import { phoneText, projectLabel } from "./telegram-status.js";
|
|
12
|
-
import { CHAT_CONTROLS, chatControlHref,
|
|
12
|
+
import { CHAT_CONTROLS, chatControlHref, isChatControl, } from "./chat-controls.js";
|
|
13
13
|
import { CHAT_TASK_ACTIONS, isChatTaskAction } from "./chat-task-actions.js";
|
|
14
14
|
export function channelRepos(store, approver, registry) {
|
|
15
15
|
const seen = new Set();
|
package/dist/chat-delivery.js
CHANGED
|
@@ -23,7 +23,6 @@ import { phoneText, PHONE_HELP, phoneCommand, phoneStatus, phoneTaskView, phoneT
|
|
|
23
23
|
import { applyRoomInbound, conversationRow, roomCardApprover, roomCommand, roomGrantAllowed, roomMessagesAfter, roomMessageText, teamDomain } from "./chat-rooms.js";
|
|
24
24
|
import { isTelegramProgressNotification, proposalTaskOf } from "./store.js";
|
|
25
25
|
import { isShotsKind, resultShotsFor } from "./result-shots.js";
|
|
26
|
-
import { CHAT_APP_NAMES } from "./flow-triggers.js";
|
|
27
26
|
import { answersPrompt, applyDecideFeedback, applyDecideTap, decideFallbackLink, decideOffer, decideTargetOf, hasLiveDecideTokens, linksFor, mergedText, mintDecideButtons, offerFingerprint, openPromptFor, recordChatMerge } from "./chat-decide.js";
|
|
28
27
|
import { mergePullRequest } from "./pull-request-flow.js";
|
|
29
28
|
import { attentionSubjectOf, neverHeld, watchedPing } from "./agent-board.js";
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** A pause that ends early, and quietly, when `signal` aborts. */
|
|
2
|
+
export declare const abortablePause: (signal: AbortSignal) => (ms: number) => Promise<void>;
|
|
3
|
+
/**
|
|
4
|
+
* Works the queue while this connection stays the current, leased one: each pass takes in saved events, plans room
|
|
5
|
+
* messages, delivers one part and plans notifications (when they are on), checking it is still current between steps.
|
|
6
|
+
* A pass waits while the connection isn't ready (offline, or the provider asked to retry later).
|
|
7
|
+
*/
|
|
8
|
+
export declare function workChatQueue(steps: {
|
|
9
|
+
same: () => boolean;
|
|
10
|
+
ready: () => boolean;
|
|
11
|
+
pause: (ms: number) => Promise<void>;
|
|
12
|
+
notifications: () => boolean;
|
|
13
|
+
process: () => Promise<unknown>;
|
|
14
|
+
rooms: () => Promise<unknown>;
|
|
15
|
+
deliver: () => Promise<unknown>;
|
|
16
|
+
notify: () => Promise<unknown>;
|
|
17
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** The loop every hosted chat connection (Slack, Discord, Teams) runs over its saved queue. */
|
|
2
|
+
import { setTimeout as sleep } from "node:timers/promises";
|
|
3
|
+
/** A pause that ends early, and quietly, when `signal` aborts. */
|
|
4
|
+
export const abortablePause = (signal) => async (ms) => {
|
|
5
|
+
try {
|
|
6
|
+
await sleep(ms, undefined, { signal });
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
/* shutdown */
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Works the queue while this connection stays the current, leased one: each pass takes in saved events, plans room
|
|
14
|
+
* messages, delivers one part and plans notifications (when they are on), checking it is still current between steps.
|
|
15
|
+
* A pass waits while the connection isn't ready (offline, or the provider asked to retry later).
|
|
16
|
+
*/
|
|
17
|
+
export async function workChatQueue(steps) {
|
|
18
|
+
while (steps.same()) {
|
|
19
|
+
if (!steps.ready()) {
|
|
20
|
+
await steps.pause(1000);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
await steps.process();
|
|
24
|
+
if (steps.same())
|
|
25
|
+
await steps.rooms();
|
|
26
|
+
if (steps.same())
|
|
27
|
+
await steps.deliver();
|
|
28
|
+
if (steps.same() && steps.notifications())
|
|
29
|
+
await steps.notify();
|
|
30
|
+
await steps.pause(1000);
|
|
31
|
+
}
|
|
32
|
+
}
|
package/dist/chat-messages.js
CHANGED
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Imports the store's types only (no runtime cycle): the store builds its chats on it.
|
|
10
10
|
*/
|
|
11
|
-
import { join } from "node:path";
|
|
12
11
|
import { neverHeld, watchedPing } from "./agent-board.js";
|
|
13
12
|
import { ChatCore } from "./chat-core.js";
|
|
14
13
|
import { readChatMessageBody, readChatPart, savedChatEventBody, savedChatPart } from "./contracts/chat-content.js";
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { Store } from "./store.js";
|
|
2
|
+
import { type Html } from "./html.js";
|
|
3
|
+
import { type ChatIdentity } from "./chat-delivery-state.js";
|
|
4
|
+
export type ChatSettingsOptions = {
|
|
5
|
+
code?: string;
|
|
6
|
+
problem?: string;
|
|
7
|
+
now?: Date;
|
|
8
|
+
who?: string;
|
|
9
|
+
};
|
|
10
|
+
/** What differs between the Slack and Discord settings pages; everything else is one page. */
|
|
11
|
+
export type ChatSettingsPage = {
|
|
12
|
+
provider: "slack" | "discord";
|
|
13
|
+
label: string;
|
|
14
|
+
credentials: ChatIdentity | null;
|
|
15
|
+
/** How to create the app, shown before it is connected. */
|
|
16
|
+
setup: Html;
|
|
17
|
+
/** The connect form's fields around the password. */
|
|
18
|
+
connect: (password: Html) => Html;
|
|
19
|
+
/** The "Access and setup" paragraphs. */
|
|
20
|
+
access: Html;
|
|
21
|
+
};
|
|
22
|
+
/** Settings → Slack or Discord: connect the app once, then each teammate pairs their own account. */
|
|
23
|
+
export declare function chatSettingsHtml(store: Store, dir: string, options: ChatSettingsOptions, page: ChatSettingsPage): Html;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { loadPrimary } from "./webhooks.js";
|
|
2
|
+
import { html, postForm } from "./html.js";
|
|
3
|
+
import { ChatState } from "./chat-delivery-state.js";
|
|
4
|
+
/** Settings → Slack or Discord: connect the app once, then each teammate pairs their own account. */
|
|
5
|
+
export function chatSettingsHtml(store, dir, options, page) {
|
|
6
|
+
const { provider, label, credentials } = page, state = new ChatState(store, provider), bindings = credentials ? state.bindings(credentials.installation).filter(one => state.live(one)) : [], binding = options.who === undefined ? null : bindings.find(one => one.approver === options.who) ?? null, others = bindings.length - (binding === null ? 0 : 1);
|
|
7
|
+
const runtime = credentials ? state.runtime(credentials.installation) : null;
|
|
8
|
+
const now = options.now ?? new Date(), live = runtime &&
|
|
9
|
+
runtime.leaseUntil !== null &&
|
|
10
|
+
runtime.leaseUntil > now.toISOString() &&
|
|
11
|
+
runtime.connected;
|
|
12
|
+
const password = html `<label>Your Toolroll password<input style="min-height:44px" type="password" name="password" autocomplete="current-password" required></label>`;
|
|
13
|
+
const post = (action, content) => postForm(`/settings/${provider}/${action}`, content, { attrs: { class: "card" } });
|
|
14
|
+
const parts = [];
|
|
15
|
+
if (!credentials) {
|
|
16
|
+
parts.push(html `<p>Manage projects and review results in a private ${label} conversation.</p>`, page.setup, post("connect", page.connect(password)));
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
parts.push(html `<p><strong>${credentials.workspace}</strong> · ${live ? "Connected" : "Waiting for connection"}</p>`);
|
|
20
|
+
if (runtime?.problem)
|
|
21
|
+
parts.push(html `<p role="status">${String(runtime.problem)}</p>`);
|
|
22
|
+
if (!live && !runtime?.problem)
|
|
23
|
+
parts.push(html `<p>Keep the Toolroll worker running to receive ${label} messages.</p>`);
|
|
24
|
+
if (options.code) {
|
|
25
|
+
parts.push(html `<h2>Pair your account</h2><p>Send this in a direct message to your Toolroll ${label} app. It expires in 10 minutes.</p>`, html `<label>Pairing message<input type="text" readonly value="pair ${options.code}" autocomplete="off" style="width:100%;max-width:100%;font-size:14px;min-height:44px"></label>`, html `<a class="button-link" style="min-height:44px;white-space:nowrap" href="/settings/${provider}">Check connection</a>`);
|
|
26
|
+
}
|
|
27
|
+
else if (binding && state.live(binding)) {
|
|
28
|
+
const { pending, dropped: failed } = state.partCounts(binding.id);
|
|
29
|
+
parts.push(html `<p>Your ${label} account is paired.${others ? ` ${others} teammate${others === 1 ? " is" : "s are"} paired too.` : ""}${pending ? ` ${pending} replies waiting to send.` : ""}${failed ? ` ${failed} replies could not be delivered. Open the saved chat to recover them.` : ""}</p>`, html `<p>Try “What needs my attention?” or “Show the evidence for the latest result.”</p>`, html `<p><a href="/chat">Open saved chat</a></p>`, loadPrimary(process.env, dir) === provider
|
|
30
|
+
? html `<p>Task updates are sent here.</p>`
|
|
31
|
+
: post("alerts", html `<button type="submit">Send task updates here</button>`));
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
parts.push(post("pair", html `<h2>Pair your account</h2><p>Your paired ${label} account can read your connected projects and confirm proposed changes. Password approvals still open in Toolroll.${others ? ` ${others} teammate${others === 1 ? " is" : "s are"} already paired.` : ""}</p>${password}<button type="submit">Create pairing code</button>`));
|
|
35
|
+
}
|
|
36
|
+
if (binding && state.live(binding) && !options.code)
|
|
37
|
+
parts.push(post("unpair", html `${password}<p class="meta">Unpairing ends every open button in your chat. Teammates are unaffected.</p><button type="submit">Unpair my account</button>`));
|
|
38
|
+
parts.push(html `<details><summary>Connection settings</summary>${post("disconnect", html `${password}<button type="submit">Disconnect ${label}</button>`)}</details>`);
|
|
39
|
+
}
|
|
40
|
+
return html `<section style="max-width:42rem;overflow-wrap:anywhere"><p><a href="/settings">Settings</a></p><h1>${label}</h1>${options.problem ? html `<p class="problem" role="alert">${options.problem}</p>` : ""}${parts}<details><summary>Access and setup</summary>${page.access}</details></section>`;
|
|
41
|
+
}
|
package/dist/claim.js
CHANGED
|
@@ -749,7 +749,7 @@ export const MAX_STRIKES = 3;
|
|
|
749
749
|
function sealInterrupted(store, leaseId, args) {
|
|
750
750
|
const { runId, taskId, stopRun, now } = args;
|
|
751
751
|
const db = store.handle;
|
|
752
|
-
|
|
752
|
+
openAttempt(store, leaseId, runId, "an interruption");
|
|
753
753
|
// A lease the world moved past still ends its run as interrupted — that IS the honest ending — touching no task state.
|
|
754
754
|
const fenced = !releaseAs(store, leaseId, "interrupted", now);
|
|
755
755
|
if (args.message !== undefined && args.message.trim() !== "") {
|
|
@@ -797,7 +797,7 @@ function sealInterrupted(store, leaseId, args) {
|
|
|
797
797
|
function sealHandBack(store, leaseId, args) {
|
|
798
798
|
const { runId, taskId, now } = args;
|
|
799
799
|
const db = store.handle;
|
|
800
|
-
|
|
800
|
+
openAttempt(store, leaseId, runId, "a hand-back");
|
|
801
801
|
// A lease the world moved past still ends its run as interrupted — that IS the honest ending — touching no task state.
|
|
802
802
|
const fenced = !releaseAs(store, leaseId, "interrupted", now);
|
|
803
803
|
if (args.message.trim() !== "")
|
|
@@ -974,7 +974,6 @@ const PLAN_BACKOFF_MS = [60_000, 2 * 60_000, 4 * 60_000];
|
|
|
974
974
|
*/
|
|
975
975
|
function sealPlan(store, leaseId, args) {
|
|
976
976
|
const { runId, taskId, plan, now } = args;
|
|
977
|
-
const db = store.handle;
|
|
978
977
|
// The ingestion record (contract handoff, task 1) is composed here from
|
|
979
978
|
// the filed terms and the drafted plan; its file and row land inside the
|
|
980
979
|
// transaction below, only after the stale-source check admits the draft.
|
|
@@ -1199,7 +1198,7 @@ function describeSourceDrift(source, current) {
|
|
|
1199
1198
|
*/
|
|
1200
1199
|
function sealRevision(store, leaseId, args) {
|
|
1201
1200
|
const { runId, taskId, taskRef, revision, now } = args;
|
|
1202
|
-
|
|
1201
|
+
openAttempt(store, leaseId, runId, "a plan revision", "builder");
|
|
1203
1202
|
const stopped = interruptIfStopped(store, { leaseId, runId, taskId, now });
|
|
1204
1203
|
if (stopped !== null)
|
|
1205
1204
|
return { ok: false, reason: "stopped" };
|
|
@@ -1249,7 +1248,6 @@ function sealRevision(store, leaseId, args) {
|
|
|
1249
1248
|
*/
|
|
1250
1249
|
function sealPlanFailure(store, leaseId, args) {
|
|
1251
1250
|
const { runId, taskId, failure: kind, message, now } = args;
|
|
1252
|
-
const db = store.handle;
|
|
1253
1251
|
const run = openAttempt(store, leaseId, runId, "a failure", "planner");
|
|
1254
1252
|
const stopped = interruptIfStopped(store, { leaseId, runId, taskId, now });
|
|
1255
1253
|
if (stopped !== null)
|
|
@@ -1331,7 +1329,6 @@ function sealPlanFailure(store, leaseId, args) {
|
|
|
1331
1329
|
*/
|
|
1332
1330
|
function sealScout(store, leaseId, args) {
|
|
1333
1331
|
const { runId, taskId, report, now } = args;
|
|
1334
|
-
const db = store.handle;
|
|
1335
1332
|
const run = openAttempt(store, leaseId, runId, "a report", "scout");
|
|
1336
1333
|
const stopped = interruptIfStopped(store, { leaseId, runId, taskId, now });
|
|
1337
1334
|
if (stopped !== null)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ExecResult, run } from "./exec.js";
|
|
2
|
+
export type CommandRunner = (file: string, args: readonly string[], options: Parameters<typeof run>[2]) => Promise<ExecResult>;
|
|
3
|
+
/** No tools, no permission prompts, no MCP servers, nothing kept: a `claude -p` call that can only answer. */
|
|
4
|
+
export declare const CLAUDE_NO_TOOLS_ARGS: readonly string[];
|
|
5
|
+
/** `--model <model>`, or nothing for the CLI's own default. */
|
|
6
|
+
export declare const claudeModelArgs: (model: string) => string[];
|
|
7
|
+
/** What a failed call's output says when this computer's Claude isn't signed in. */
|
|
8
|
+
export declare const CLAUDE_SIGNED_OUT: RegExp;
|
|
9
|
+
/** `claude -p --output-format json` stdout as its one object, or null. */
|
|
10
|
+
export declare function claudeJsonBody(stdout: string, cap: number): Record<string, unknown> | null;
|
|
11
|
+
/** Runs `work` in a fresh temporary folder named `<prefix>…`, removed afterwards whatever happens. */
|
|
12
|
+
export declare function inScratchDir<T>(prefix: string, work: (dir: string) => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The parts every bare `claude -p` call shares: a throwaway working folder, the flags that leave it with no tools,
|
|
3
|
+
* no MCP servers and no saved session, and reading its one JSON reply. Each caller still builds its own argv around
|
|
4
|
+
* these, in its own order.
|
|
5
|
+
*/
|
|
6
|
+
import { Buffer } from "node:buffer";
|
|
7
|
+
import { mkdtempSync, rmSync } from "node:fs";
|
|
8
|
+
import { tmpdir } from "node:os";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { strictJsonParse } from "./converse.js";
|
|
11
|
+
/** No tools, no permission prompts, no MCP servers, nothing kept: a `claude -p` call that can only answer. */
|
|
12
|
+
export const CLAUDE_NO_TOOLS_ARGS = [
|
|
13
|
+
"--safe-mode", "--no-session-persistence", "--tools", "", "--permission-mode", "dontAsk",
|
|
14
|
+
"--permission-prompts", "none", "--strict-mcp-config", "--mcp-config", '{"mcpServers":{}}',
|
|
15
|
+
];
|
|
16
|
+
/** `--model <model>`, or nothing for the CLI's own default. */
|
|
17
|
+
export const claudeModelArgs = (model) => (model === "default" ? [] : ["--model", model]);
|
|
18
|
+
/** What a failed call's output says when this computer's Claude isn't signed in. */
|
|
19
|
+
export const CLAUDE_SIGNED_OUT = /not logged in|login|authenticat/i;
|
|
20
|
+
/** `claude -p --output-format json` stdout as its one object, or null. */
|
|
21
|
+
export function claudeJsonBody(stdout, cap) {
|
|
22
|
+
const parsed = strictJsonParse(Buffer.from(stdout, "utf8"), cap, 12);
|
|
23
|
+
return parsed.ok && typeof parsed.value === "object" && parsed.value !== null && !Array.isArray(parsed.value) ? parsed.value : null;
|
|
24
|
+
}
|
|
25
|
+
/** Runs `work` in a fresh temporary folder named `<prefix>…`, removed afterwards whatever happens. */
|
|
26
|
+
export async function inScratchDir(prefix, work) {
|
|
27
|
+
const dir = mkdtempSync(join(tmpdir(), prefix));
|
|
28
|
+
try {
|
|
29
|
+
return await work(dir);
|
|
30
|
+
}
|
|
31
|
+
finally {
|
|
32
|
+
rmSync(dir, { recursive: true, force: true });
|
|
33
|
+
}
|
|
34
|
+
}
|
package/dist/cli-http.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ import type { Store } from './store.js';
|
|
|
3
3
|
import type { Principal } from './operate.js';
|
|
4
4
|
import { type RemoteCommandLookup } from './remote-command.js';
|
|
5
5
|
import { type Admission, type BudgetLimit } from './request-budget.js';
|
|
6
|
-
import type { SessionRunner } from './session-remote.js';
|
|
7
6
|
/**
|
|
8
7
|
* `POST /api/cli` (remote CLI, Phase 1): a teammate's laptop sends the argv it would have run locally, and the central
|
|
9
8
|
* server runs that same command as that person. There is no per-resource API here and no second policy: the transport
|
|
@@ -12,10 +11,6 @@ import type { SessionRunner } from './session-remote.js';
|
|
|
12
11
|
*
|
|
13
12
|
* Only `Authorization: Bearer so_…` is accepted — never a password, cookie or the owner's saved login — and a request
|
|
14
13
|
* carrying a browser Origin is refused, so a page can never drive it.
|
|
15
|
-
*
|
|
16
|
-
* `session …` (D7) is the one command the server answers through its own owner rather than runOperateAs: native
|
|
17
|
-
* session operations need the server's live coding workspace (session-remote.ts). They are admitted, budgeted and
|
|
18
|
-
* refused here exactly like every other command.
|
|
19
14
|
*/
|
|
20
15
|
/** Who a remote command runs as, and the runner: operate.ts's own types, so a change there breaks this build instead of
|
|
21
16
|
* drifting. Type-only: the value is loaded lazily through `run` (operate.ts imports the server that imports this). */
|
|
@@ -50,8 +45,6 @@ export type CliHttpOptions = {
|
|
|
50
45
|
modeOf?: RemoteCommandLookup | undefined;
|
|
51
46
|
/** The token's request budget (request-budget.ts), charged once per request before its body is read. */
|
|
52
47
|
admit?: ((principal: Principal) => Admission) | undefined;
|
|
53
|
-
/** Native session operations (`session …`), run by the server's session owner; absent where none is ready. */
|
|
54
|
-
session?: SessionRunner | undefined;
|
|
55
48
|
store: Store;
|
|
56
49
|
};
|
|
57
50
|
/** Decode the request's shape and byte limits. The endpoint checks file references against its command policy. */
|