@amistio/cli 0.1.31 → 0.1.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/index.js +190 -6
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -17,6 +17,8 @@ Runner Update installs the official `@amistio/cli` package and then refreshes th
|
|
|
17
17
|
|
|
18
18
|
Current runners advertise the work kinds they can claim. Older runners that do not send this capability can continue legacy brain generation, implementation, and plan revision work, but they will skip source-aware assistant, impact-preview, semantic brain consolidation, project-context refresh, issue-diagnosis, app-evaluation, security-posture, Test-quality, implementation-Test-gate, and implementation-verification work until updated.
|
|
19
19
|
|
|
20
|
+
Tool session reuse is bounded. One-shot tool sessions close after successful completion, failed runs are blocked, active sessions are treated as already in use, and reusable sessions idle for more than six hours are closed before the next claim selects context. This keeps follow-up work from inheriting stale local AI context while preserving recent reusable sessions for related work.
|
|
21
|
+
|
|
20
22
|
Repository brain auto-sync is disabled until the repository link option is enabled in the app. After pairing, run `amistio sync watch` from the paired checkout to push recognized external brain Markdown/MDX files and explicit HTML artifacts under `docs/html/<area>/`, including local ADRs, plans, prompts, workflows, memory, context, architecture, and feature docs, to the app for review. Markdown is the default generation format; HTML appears only when a runner or user explicitly generated an HTML artifact. `amistio run --watch` also runs the same cycle between work polls when the option is enabled. The CLI skips templates, unsupported paths, oversized files, unchanged managed docs, and conflicts instead of silently overwriting web state.
|
|
21
23
|
|
|
22
24
|
Repository autopilot is disabled until the repository link option is enabled in the app. When enabled, Amistio can attach an audited low-risk autopilot authorization to eligible runner work, including generated brain approval, impact preview, issue diagnosis, security posture scan, app evaluation cleanup, low-risk implementation handoff, requeue, and implementation verification. The Runner panel shows and updates safe work scopes, allowed candidate types, max risk, optional runner binding, daily/concurrent/failure budgets, expiry/review/cooldown windows, and pause state. The CLI shows authorization id, candidate id/type, outcome, policy version, and work kind in `amistio work list`, claim logs, runner prompts, and milestone activity. Autopilot does not widen local runner permissions: pairing, supported work kinds, runner identity, Git worktree isolation, redaction, local-tool permission controls, and unsafe/review-required/blocked/paused/budget stops still apply.
|
|
@@ -36,6 +38,7 @@ amistio run --watch --tool opencode --provider anthropic --model-id claude-opus-
|
|
|
36
38
|
amistio run --watch --max-concurrent-work 2 --tool opencode
|
|
37
39
|
amistio run --watch --background --tool opencode
|
|
38
40
|
amistio runner status
|
|
41
|
+
amistio runner smoke-session-lifecycle
|
|
39
42
|
```
|
|
40
43
|
|
|
41
44
|
Provider-backed model preferences use sanitized catalog fields: `--provider`, `--model-id`, optional `--model-variant`, and `--reasoning-effort` (`auto`, `low`, `medium`, `high`, or `xhigh`). Opencode catalog metadata is synthesized by Amistio or derived from readable local OpenCode JSON config until opencode exposes a native catalog. Provider credentials, API keys, and local secret paths stay in the local tool configuration; they are not stored in Amistio preferences or runner heartbeats.
|
|
@@ -46,6 +49,8 @@ When `--tool codex` uses the Codex SDK, intermediate progress can be quiet until
|
|
|
46
49
|
|
|
47
50
|
`amistio runner status` reports local background runner state, latest heartbeat, and bounded resource usage when available. Resource usage is latest-sample runner process memory/CPU plus safe aggregate system memory/load signals; it does not include source files, environment variables, command lines, process lists, credentials, or arbitrary local paths.
|
|
48
51
|
|
|
52
|
+
`amistio runner smoke-session-lifecycle` runs a local no-claim smoke for the runner tool-session lifecycle. It does not contact the API, claim production work, inspect source, or mutate local runner state; it verifies that completed one-shot sessions close, active sessions are treated as in use, stale sessions are not selected for reuse, and fresh related reusable sessions can still continue.
|
|
53
|
+
|
|
49
54
|
The runner advertises its supported work kinds in heartbeats. Current runners can claim read-only `projectContextRefresh` jobs from the workspace Context panel and create due runner-driven refreshes when no fresh approved map exists. Context refreshes inspect the paired checkout locally without modifying files and submit only bounded summaries, slices, entities, relations, safe citations, confidence, freshness, and repo-relative paths. If a submitted context refresh contains unsafe evidence, unsafe paths, or a map too large to store safely, Amistio marks the refresh failed with a safe reason instead of storing the rejected raw result. Approved maps are reused as context packs for source-aware assistant and impact-preview work. Current runners can also claim read-only issue diagnosis jobs from the web Issues panel, generate root-cause analysis and a proposed fix, and submit that result without modifying source. They can claim manual read-only `appEvaluationScan` jobs from the workspace Evaluate panel and create at most one due hourly evaluation during normal watch/background polling when app evaluation is enabled for the repository link. Evaluation results contain bounded summaries, safe evidence, suggested actions, lifecycle proposals, and repo-relative paths only. Current runners can also claim manual read-only `securityPostureScan` jobs from the workspace Security panel and create due daily posture checks during normal watch/background polling. Security scan results contain bounded summaries, standard references, safe evidence, and repo-relative paths only. Current runners can claim manual read-only `testQualityScan` jobs from the workspace Test panel and create one due daily Test scan per repository when Test quality is enabled. Test scans run only existing lint, typecheck, test, coverage, build, or verify commands and submit bounded command summaries, coverage summaries, safe findings, blocked reasons, warnings, and repo-relative paths. Missing tests, missing coverage, low coverage, failing checks, flaky tests, and test gaps create reviewable plan-backed findings in the app. Current runners also claim `implementationTestGate` jobs before implementation completion, PR handoff, or runner-managed push; a passing gate is required unless the web Test panel records an audited override. Blocked implementation Test gates submit structured Test findings, such as `blockedEnvironment`, with safe evidence, a suggested action, and a verification plan. Current runners can claim read-only `implementationVerification` jobs from Tasks to prove whether completed implementation work actually landed; verification submits bounded acceptance-criteria evidence, checks, gaps, outcome, and recommendation without mutating source. Source, secrets, environment variables, command lines, process lists, credentials, provider sessions, and arbitrary local paths stay local. Implementation or cleanup is queued separately only after the user approves an issue analysis, app evaluation finding, security remediation plan, or Test quality plan in the app.
|
|
50
55
|
|
|
51
56
|
Approved implementation work uses Git as the handoff boundary. During worktree preflight, the runner locally copies eligible ignored root dotenv files such as `.env.local` or `.env.test.local` from the paired checkout into the implementation worktree when the target is missing and ignored, so local tests can use the same machine configuration. Dotenv values, variable names, file contents, and local paths are not uploaded to Amistio, and copied dotenv files stay ignored so PR handoff does not commit them. After the local tool completes successfully, the runner materializes approved Markdown, MDX, and HTML project-brain artifacts for the same work scope into the isolated worktree before final Git status. It then commits all source and artifact changes, fetches and rebases from the linked remote's default branch, pushes an `amistio/work/...` branch, opens or reuses a pull request with the locally authenticated `gh` CLI, reports only safe PR and artifact-inclusion metadata to Amistio, and removes the local worktree after the PR URL is durable. Artifact-only materialization changes still create or reuse a PR; no-change completion requires no source changes and no approved artifact changes. Prepare the runner machine with Git commit identity, fetch/push permission to the linked remote, and `gh auth status`. If artifact materialization, commit, fetch/rebase, push, or PR creation fails, the work item is blocked and the branch/worktree stay on disk for manual recovery; source files and patches are not uploaded to Amistio.
|
package/dist/index.js
CHANGED
|
@@ -4740,8 +4740,32 @@ function runProcess(command, args, timeoutMs) {
|
|
|
4740
4740
|
});
|
|
4741
4741
|
}
|
|
4742
4742
|
|
|
4743
|
+
// src/tool-session-lifecycle.ts
|
|
4744
|
+
var TOOL_SESSION_MAX_IDLE_MS = 6 * 60 * 60 * 1e3;
|
|
4745
|
+
function completedToolSessionStatus(session) {
|
|
4746
|
+
return session.resumabilityScope === "none" ? "closed" : "open";
|
|
4747
|
+
}
|
|
4748
|
+
function completedToolSessionClosedReason(session) {
|
|
4749
|
+
if (session.resumabilityScope !== "none") {
|
|
4750
|
+
return void 0;
|
|
4751
|
+
}
|
|
4752
|
+
return "Completed one-shot tool run; this session is not reusable.";
|
|
4753
|
+
}
|
|
4754
|
+
function staleToolSessionClosedReason(session, now = /* @__PURE__ */ new Date()) {
|
|
4755
|
+
if (session.status !== "open") {
|
|
4756
|
+
return void 0;
|
|
4757
|
+
}
|
|
4758
|
+
const lastActivityMs = Date.parse(session.lastActivityAt);
|
|
4759
|
+
if (!Number.isFinite(lastActivityMs)) {
|
|
4760
|
+
return "Session has an invalid last activity timestamp; closing to prevent stale context reuse.";
|
|
4761
|
+
}
|
|
4762
|
+
if (lastActivityMs + TOOL_SESSION_MAX_IDLE_MS >= now.getTime()) {
|
|
4763
|
+
return void 0;
|
|
4764
|
+
}
|
|
4765
|
+
return "Session idle window expired; closing to prevent stale context reuse.";
|
|
4766
|
+
}
|
|
4767
|
+
|
|
4743
4768
|
// src/session-policy.ts
|
|
4744
|
-
var maxIdleMs = 24 * 60 * 60 * 1e3;
|
|
4745
4769
|
var maxTotalMs = 7 * 24 * 60 * 60 * 1e3;
|
|
4746
4770
|
var maxMessageCount = 80;
|
|
4747
4771
|
var maxEstimatedTokens = 12e4;
|
|
@@ -4825,10 +4849,10 @@ function sessionIneligibleReason(session, input, now) {
|
|
|
4825
4849
|
if (input.workItem.executionWorktreeKey && session.executionWorktreeKey !== input.workItem.executionWorktreeKey) {
|
|
4826
4850
|
return "worktree scope mismatch";
|
|
4827
4851
|
}
|
|
4828
|
-
if (session.status === "closed" || session.status === "archived" || session.status === "blocked" || session.status === "unavailable") {
|
|
4852
|
+
if (session.status === "closed" || session.status === "archived" || session.status === "blocked" || session.status === "unavailable" || session.status === "active") {
|
|
4829
4853
|
return `session is ${session.status}`;
|
|
4830
4854
|
}
|
|
4831
|
-
if (Date.parse(session.lastActivityAt) +
|
|
4855
|
+
if (Date.parse(session.lastActivityAt) + TOOL_SESSION_MAX_IDLE_MS < now.getTime()) {
|
|
4832
4856
|
return "session is idle past the reuse window";
|
|
4833
4857
|
}
|
|
4834
4858
|
if (Date.parse(session.createdAt) + maxTotalMs < now.getTime()) {
|
|
@@ -4873,6 +4897,134 @@ function tokens(value) {
|
|
|
4873
4897
|
return value.toLowerCase().split(/[^a-z0-9]+/).filter((token) => token.length >= 4);
|
|
4874
4898
|
}
|
|
4875
4899
|
|
|
4900
|
+
// src/runner-activation-smoke.ts
|
|
4901
|
+
function runRunnerActivationSmoke(now = /* @__PURE__ */ new Date()) {
|
|
4902
|
+
const reusableSession = createSmokeSession({ now, status: "open", lastActivityAt: new Date(now.getTime() - 60 * 60 * 1e3).toISOString() });
|
|
4903
|
+
const activeSession = createSmokeSession({ now, status: "active", lastActivityAt: new Date(now.getTime() - 60 * 60 * 1e3).toISOString() });
|
|
4904
|
+
const staleOpenSession = createSmokeSession({ now, status: "open", lastActivityAt: new Date(now.getTime() - 7 * 60 * 60 * 1e3).toISOString() });
|
|
4905
|
+
const workItem = createSmokeWorkItem(now);
|
|
4906
|
+
const oneShotStatus = completedToolSessionStatus({ resumabilityScope: "none" });
|
|
4907
|
+
const oneShotClosedReason = completedToolSessionClosedReason({ resumabilityScope: "none" });
|
|
4908
|
+
const reusableStatus = completedToolSessionStatus({ resumabilityScope: "localMachine" });
|
|
4909
|
+
const activeSelection = selectToolSession({
|
|
4910
|
+
policy: "auto",
|
|
4911
|
+
workItem,
|
|
4912
|
+
sessions: [activeSession],
|
|
4913
|
+
toolName: "opencode",
|
|
4914
|
+
runnerId: "runner_smoke",
|
|
4915
|
+
repositoryLinkId: "repo_smoke",
|
|
4916
|
+
machineId: "machine_smoke",
|
|
4917
|
+
supportsSessionReuse: true,
|
|
4918
|
+
now
|
|
4919
|
+
});
|
|
4920
|
+
const staleSelection = selectToolSession({
|
|
4921
|
+
policy: "auto",
|
|
4922
|
+
workItem,
|
|
4923
|
+
sessions: [staleOpenSession],
|
|
4924
|
+
toolName: "opencode",
|
|
4925
|
+
runnerId: "runner_smoke",
|
|
4926
|
+
repositoryLinkId: "repo_smoke",
|
|
4927
|
+
machineId: "machine_smoke",
|
|
4928
|
+
supportsSessionReuse: true,
|
|
4929
|
+
now
|
|
4930
|
+
});
|
|
4931
|
+
const reusableSelection = selectToolSession({
|
|
4932
|
+
policy: "auto",
|
|
4933
|
+
workItem,
|
|
4934
|
+
sessions: [reusableSession],
|
|
4935
|
+
toolName: "opencode",
|
|
4936
|
+
runnerId: "runner_smoke",
|
|
4937
|
+
repositoryLinkId: "repo_smoke",
|
|
4938
|
+
machineId: "machine_smoke",
|
|
4939
|
+
supportsSessionReuse: true,
|
|
4940
|
+
now
|
|
4941
|
+
});
|
|
4942
|
+
const staleClosedReason = staleToolSessionClosedReason(staleOpenSession, now);
|
|
4943
|
+
const checks = [
|
|
4944
|
+
{
|
|
4945
|
+
name: "completed-one-shot-closes",
|
|
4946
|
+
passed: oneShotStatus === "closed" && Boolean(oneShotClosedReason),
|
|
4947
|
+
detail: `completed one-shot status=${oneShotStatus}`
|
|
4948
|
+
},
|
|
4949
|
+
{
|
|
4950
|
+
name: "completed-reusable-stays-open",
|
|
4951
|
+
passed: reusableStatus === "open",
|
|
4952
|
+
detail: `completed reusable status=${reusableStatus}`
|
|
4953
|
+
},
|
|
4954
|
+
{
|
|
4955
|
+
name: "active-session-not-reused",
|
|
4956
|
+
passed: activeSelection.decision === "created",
|
|
4957
|
+
detail: `active selection decision=${activeSelection.decision}`
|
|
4958
|
+
},
|
|
4959
|
+
{
|
|
4960
|
+
name: "stale-session-not-reused",
|
|
4961
|
+
passed: staleSelection.decision === "created" && Boolean(staleClosedReason),
|
|
4962
|
+
detail: `stale selection decision=${staleSelection.decision}`
|
|
4963
|
+
},
|
|
4964
|
+
{
|
|
4965
|
+
name: "fresh-reusable-session-reused",
|
|
4966
|
+
passed: reusableSelection.decision === "continued",
|
|
4967
|
+
detail: `fresh reusable selection decision=${reusableSelection.decision}`
|
|
4968
|
+
}
|
|
4969
|
+
];
|
|
4970
|
+
return {
|
|
4971
|
+
checkedAt: now.toISOString(),
|
|
4972
|
+
passed: checks.every((check) => check.passed),
|
|
4973
|
+
checks
|
|
4974
|
+
};
|
|
4975
|
+
}
|
|
4976
|
+
function createSmokeWorkItem(now) {
|
|
4977
|
+
const timestamp = now.toISOString();
|
|
4978
|
+
return {
|
|
4979
|
+
id: "work_smoke",
|
|
4980
|
+
type: "workItem",
|
|
4981
|
+
schemaVersion: 1,
|
|
4982
|
+
accountId: "acct_smoke",
|
|
4983
|
+
projectId: "proj_smoke",
|
|
4984
|
+
workItemId: "work_smoke",
|
|
4985
|
+
workKind: "implementation",
|
|
4986
|
+
status: "running",
|
|
4987
|
+
title: "Implement runner session smoke",
|
|
4988
|
+
requestedBy: "user_smoke",
|
|
4989
|
+
requestedByUserId: "user_smoke",
|
|
4990
|
+
attempt: 1,
|
|
4991
|
+
idempotencyKey: "smoke_session_lifecycle",
|
|
4992
|
+
sessionGroupKey: "session_smoke",
|
|
4993
|
+
lastStatusAt: timestamp,
|
|
4994
|
+
createdAt: timestamp,
|
|
4995
|
+
updatedAt: timestamp
|
|
4996
|
+
};
|
|
4997
|
+
}
|
|
4998
|
+
function createSmokeSession({ now, status, lastActivityAt }) {
|
|
4999
|
+
const timestamp = now.toISOString();
|
|
5000
|
+
return {
|
|
5001
|
+
id: `tool_session_smoke_${status}`,
|
|
5002
|
+
type: "toolSession",
|
|
5003
|
+
schemaVersion: 1,
|
|
5004
|
+
accountId: "acct_smoke",
|
|
5005
|
+
projectId: "proj_smoke",
|
|
5006
|
+
toolSessionId: `tool_session_smoke_${status}`,
|
|
5007
|
+
repositoryLinkId: "repo_smoke",
|
|
5008
|
+
tool: "opencode",
|
|
5009
|
+
provider: "opencode",
|
|
5010
|
+
resumabilityScope: "localMachine",
|
|
5011
|
+
title: "Implement runner session smoke",
|
|
5012
|
+
summary: "Smoke test session",
|
|
5013
|
+
tags: ["runner", "session", "smoke"],
|
|
5014
|
+
relatedDocumentIds: [],
|
|
5015
|
+
status,
|
|
5016
|
+
runnerId: "runner_smoke",
|
|
5017
|
+
machineId: "machine_smoke",
|
|
5018
|
+
lastWorkItemId: "work_previous",
|
|
5019
|
+
lastActivityAt,
|
|
5020
|
+
messageCount: 1,
|
|
5021
|
+
reusePolicy: "auto",
|
|
5022
|
+
sessionGroupKey: "session_smoke",
|
|
5023
|
+
createdAt: timestamp,
|
|
5024
|
+
updatedAt: timestamp
|
|
5025
|
+
};
|
|
5026
|
+
}
|
|
5027
|
+
|
|
4876
5028
|
// src/sync.ts
|
|
4877
5029
|
import { execFile as execFile3 } from "node:child_process";
|
|
4878
5030
|
import { createHash as createHash5 } from "node:crypto";
|
|
@@ -6035,6 +6187,7 @@ function createAppEvaluationScanPrompt(workItem, context) {
|
|
|
6035
6187
|
"- Treat active proposed, approved, ready, or in-progress plans/prompts with accepted controlling ADRs, features, or work artifacts as active backlog. Do not classify them as cleanup material solely because they are older, unexecuted, or not yet fully implemented; use keepActive when they still describe valid pending or approved work.",
|
|
6036
6188
|
"- Treat intentionally in-progress feature tracks as still-active work when their controlling plan/feature has unchecked requirements or explicit follow-up gaps. For example, a completed first implementation prompt does not make the broader feature stale if PLAN/FEAT evidence says remaining lifecycle work is still open; return proposedLifecycleAction keepActive with evidence instead of cleanup.",
|
|
6037
6189
|
"- Treat prompt frontmatter status Ready as an active execution backlog state by default, not as stale review debt. Only flag a Ready prompt for metadata correction when its controlling plan, feature, prompt index, or verification evidence unambiguously proves the prompt has already completed or been superseded.",
|
|
6190
|
+
"- Treat implemented umbrella plans that explicitly label unchecked checklist items as deferred follow-ups, future candidates, roadmap backlog, or split-out hardening phases as valid deferred backlog. Do not mark the umbrella incomplete or stale solely because those deferred items remain unchecked; use keepActive or no cleanup, and recommend a fresh focused plan only when a concrete deferred slice has current evidence and approval.",
|
|
6038
6191
|
"- When lifecycle metadata disagrees across indexes, frontmatter, feature specs, ADRs, and implementation evidence, cite the conflict and propose a metadata correction or verification step instead of archival/removal.",
|
|
6039
6192
|
"- Check missing memory or workflow updates when repeated lessons or operational rules are visible.",
|
|
6040
6193
|
"- Check release readiness, UX, accessibility, performance, reliability, and security-posture follow-through at a summary level.",
|
|
@@ -8472,6 +8625,20 @@ runner.command("status").description("Show background runner status for the pair
|
|
|
8472
8625
|
console.log(` Resource usage: ${formatRunnerResourceUsage(heartbeat?.resourceUsage)}`);
|
|
8473
8626
|
}
|
|
8474
8627
|
});
|
|
8628
|
+
runner.command("smoke-session-lifecycle").description("Run a local no-claim smoke for runner tool-session lifecycle behavior").option("--json", "Print the smoke report as JSON").action((options) => {
|
|
8629
|
+
const report = runRunnerActivationSmoke();
|
|
8630
|
+
if (options.json) {
|
|
8631
|
+
console.log(JSON.stringify(report, null, 2));
|
|
8632
|
+
} else {
|
|
8633
|
+
console.log(`Runner session lifecycle smoke: ${report.passed ? "passed" : "failed"}`);
|
|
8634
|
+
for (const check of report.checks) {
|
|
8635
|
+
console.log(` ${check.passed ? "ok" : "fail"} ${check.name}: ${check.detail}`);
|
|
8636
|
+
}
|
|
8637
|
+
}
|
|
8638
|
+
if (!report.passed) {
|
|
8639
|
+
process.exitCode = 1;
|
|
8640
|
+
}
|
|
8641
|
+
});
|
|
8475
8642
|
runner.command("stop").description("Stop a background runner for the paired repository").option("--api-url <url>", apiUrlOptionDescription, defaultApiUrl()).option("--root <path>", "Repository root", defaultRoot).option("--runner-id <runnerId>", "Runner ID to stop when multiple background runners exist").action(async (options) => {
|
|
8476
8643
|
const context = await loadPairedApiContext(options.root, options.apiUrl);
|
|
8477
8644
|
if (!context) {
|
|
@@ -11026,6 +11193,8 @@ async function prepareToolSession({
|
|
|
11026
11193
|
workItem
|
|
11027
11194
|
}) {
|
|
11028
11195
|
const { toolSessions } = await apiClient.listToolSessions(projectId);
|
|
11196
|
+
const now = /* @__PURE__ */ new Date();
|
|
11197
|
+
await closeStaleToolSessionsBestEffort(apiClient, projectId, toolSessions, now);
|
|
11029
11198
|
const selection = selectToolSession({
|
|
11030
11199
|
policy: sessionPolicy,
|
|
11031
11200
|
workItem,
|
|
@@ -11034,7 +11203,8 @@ async function prepareToolSession({
|
|
|
11034
11203
|
runnerId,
|
|
11035
11204
|
repositoryLinkId,
|
|
11036
11205
|
machineId,
|
|
11037
|
-
supportsSessionReuse
|
|
11206
|
+
supportsSessionReuse,
|
|
11207
|
+
now
|
|
11038
11208
|
});
|
|
11039
11209
|
if (selection.decision === "skipped") {
|
|
11040
11210
|
return selection;
|
|
@@ -11076,6 +11246,18 @@ async function prepareToolSession({
|
|
|
11076
11246
|
});
|
|
11077
11247
|
return { ...selection, toolSession };
|
|
11078
11248
|
}
|
|
11249
|
+
async function closeStaleToolSessionsBestEffort(apiClient, projectId, toolSessions, now) {
|
|
11250
|
+
const staleSessions = toolSessions.map((session) => ({ session, reason: staleToolSessionClosedReason(session, now) })).filter((item) => Boolean(item.reason));
|
|
11251
|
+
if (!staleSessions.length) {
|
|
11252
|
+
return;
|
|
11253
|
+
}
|
|
11254
|
+
const settlements = await Promise.allSettled(staleSessions.map(({ session, reason }) => apiClient.updateToolSession(projectId, session.toolSessionId, {
|
|
11255
|
+
status: "closed",
|
|
11256
|
+
closedReason: reason,
|
|
11257
|
+
summary: session.summary ?? reason
|
|
11258
|
+
})));
|
|
11259
|
+
logRejectedSettlements("close stale tool sessions", settlements);
|
|
11260
|
+
}
|
|
11079
11261
|
async function finalizeToolSession({
|
|
11080
11262
|
apiClient,
|
|
11081
11263
|
costUsd,
|
|
@@ -11093,8 +11275,10 @@ async function finalizeToolSession({
|
|
|
11093
11275
|
return void 0;
|
|
11094
11276
|
}
|
|
11095
11277
|
const summary = summarizeToolOutput(stdout) ?? session.summary;
|
|
11278
|
+
const nextStatus = status === "completed" ? completedToolSessionStatus(session) : "blocked";
|
|
11279
|
+
const closedReason = status === "completed" ? completedToolSessionClosedReason(session) : "Last run failed or returned a non-zero exit code.";
|
|
11096
11280
|
const { toolSession } = await apiClient.updateToolSession(projectId, session.toolSessionId, {
|
|
11097
|
-
status:
|
|
11281
|
+
status: nextStatus,
|
|
11098
11282
|
runnerId,
|
|
11099
11283
|
lastWorkItemId: workItemId,
|
|
11100
11284
|
messageCount: (session.messageCount ?? 0) + (messageCount ?? 1),
|
|
@@ -11102,7 +11286,7 @@ async function finalizeToolSession({
|
|
|
11102
11286
|
...tokensIn !== void 0 ? { estimatedInputTokens: (session.estimatedInputTokens ?? 0) + tokensIn } : {},
|
|
11103
11287
|
...tokensOut !== void 0 ? { estimatedOutputTokens: (session.estimatedOutputTokens ?? 0) + tokensOut } : {},
|
|
11104
11288
|
...costUsd !== void 0 ? { costUsd: (session.costUsd ?? 0) + costUsd } : {},
|
|
11105
|
-
...
|
|
11289
|
+
...closedReason ? { closedReason } : {}
|
|
11106
11290
|
});
|
|
11107
11291
|
return toolSession;
|
|
11108
11292
|
}
|