@awak-app/simy-cli 0.1.5 → 0.2.1
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 +13 -0
- package/package.json +1 -1
- package/src/agent.js +81 -4
- package/src/cli-contract.js +11 -0
- package/src/orchestrator/budget.js +39 -3
- package/src/orchestrator/loop.js +3 -3
- package/src/orchestrator/recovery.js +26 -4
- package/src/runner.js +30 -1
package/README.md
CHANGED
|
@@ -93,6 +93,19 @@ executor updates, repository selection, and CLI reconnection. Web clients must
|
|
|
93
93
|
validate the action type before rendering or executing it; raw local error text
|
|
94
94
|
is not copied into the recovery contract.
|
|
95
95
|
|
|
96
|
+
## Provider token budget
|
|
97
|
+
|
|
98
|
+
Each run accepts a configurable `token_budget` (or the explicit
|
|
99
|
+
`provider_token_budget` alias) from 1 to 10,000,000 Provider tokens. The default
|
|
100
|
+
is 250,000. Provider tokens are the Codex or Claude Code usage reported for the
|
|
101
|
+
run; they are a safety limit and are not billed as SIMY tokens.
|
|
102
|
+
|
|
103
|
+
When a run reaches this limit, its recovery contract suggests a higher rounded
|
|
104
|
+
limit. SIMY Web can edit that suggestion and call
|
|
105
|
+
`POST /v1/agentic-loop/:run_id/provider-token-budget` with
|
|
106
|
+
`provider_token_budget`. The CLI persists the higher limit and resumes the same
|
|
107
|
+
run. SIMY token consumption remains a separate platform billing record.
|
|
108
|
+
|
|
96
109
|
## Interactive console
|
|
97
110
|
|
|
98
111
|
Running `simy` in a terminal opens the Agentic Loop chat. It discovers the
|
package/package.json
CHANGED
package/src/agent.js
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
continueLocalCodingRun,
|
|
8
8
|
continueLocalCodingRunAfterRepositoryApproval,
|
|
9
9
|
createRun,
|
|
10
|
+
increaseLocalCodingRunProviderTokenBudget,
|
|
10
11
|
LocalRunRegistry,
|
|
11
12
|
isLocalRepositoryApprovalPending,
|
|
12
13
|
pauseLocalCodingRun,
|
|
@@ -64,6 +65,11 @@ import {
|
|
|
64
65
|
scanGitRepositories,
|
|
65
66
|
writeRepositoryInventory,
|
|
66
67
|
} from "./repository-inventory.js";
|
|
68
|
+
import {
|
|
69
|
+
CLI_API_CONTRACT_VERSION,
|
|
70
|
+
CLI_VERSION,
|
|
71
|
+
withCliContract,
|
|
72
|
+
} from "./cli-contract.js";
|
|
67
73
|
|
|
68
74
|
const DEVICE_HEARTBEAT_INTERVAL_MS = 20_000;
|
|
69
75
|
|
|
@@ -99,7 +105,11 @@ export async function startAgent({
|
|
|
99
105
|
]
|
|
100
106
|
: [],
|
|
101
107
|
);
|
|
102
|
-
const
|
|
108
|
+
const installMode = updateManager?.snapshot?.()?.install_mode ?? null;
|
|
109
|
+
const availableCapabilities = withCliContract(
|
|
110
|
+
await readCapabilities(dependencies),
|
|
111
|
+
installMode,
|
|
112
|
+
);
|
|
103
113
|
const heartbeatDevice = dependencies.heartbeatDevice || heartbeatLocalDevice;
|
|
104
114
|
const runOptions = dependencies.runOptions || {};
|
|
105
115
|
const repositoryScanRoot = resolve(
|
|
@@ -274,6 +284,8 @@ export async function startAgent({
|
|
|
274
284
|
if (req.method === "GET" && url.pathname === "/v1/health") {
|
|
275
285
|
json(res, 200, {
|
|
276
286
|
ok: true,
|
|
287
|
+
cli_version: CLI_VERSION,
|
|
288
|
+
api_contract_version: CLI_API_CONTRACT_VERSION,
|
|
277
289
|
daemon,
|
|
278
290
|
web_origin: apiOrigin,
|
|
279
291
|
session_valid: isSessionValid(session, Date.now(), apiOrigin),
|
|
@@ -283,7 +295,7 @@ export async function startAgent({
|
|
|
283
295
|
return;
|
|
284
296
|
}
|
|
285
297
|
if (req.method === "GET" && url.pathname === "/v1/capabilities") {
|
|
286
|
-
json(res, 200,
|
|
298
|
+
json(res, 200, availableCapabilities);
|
|
287
299
|
return;
|
|
288
300
|
}
|
|
289
301
|
if (req.method === "POST" && agenticLoopPath === "/v1/agentic-loop/preflight") {
|
|
@@ -356,7 +368,12 @@ export async function startAgent({
|
|
|
356
368
|
startHeartbeatTimer();
|
|
357
369
|
loginUrl = null;
|
|
358
370
|
registry.emit("change", registry.list());
|
|
359
|
-
json(res, 200, {
|
|
371
|
+
json(res, 200, {
|
|
372
|
+
ok: true,
|
|
373
|
+
expires_at: session.expires_at,
|
|
374
|
+
cli_version: CLI_VERSION,
|
|
375
|
+
api_contract_version: CLI_API_CONTRACT_VERSION,
|
|
376
|
+
});
|
|
360
377
|
void synchronizeAuthorizedSession();
|
|
361
378
|
return;
|
|
362
379
|
}
|
|
@@ -452,7 +469,7 @@ export async function startAgent({
|
|
|
452
469
|
base_branch: typeof body.base_branch === "string" ? body.base_branch : "dev",
|
|
453
470
|
max_attempts: body.max_attempts,
|
|
454
471
|
retry_budget: body.retry_budget,
|
|
455
|
-
token_budget: body.token_budget,
|
|
472
|
+
token_budget: body.provider_token_budget ?? body.token_budget,
|
|
456
473
|
ui_evidence_root:
|
|
457
474
|
typeof body.ui_evidence_root === "string" ? body.ui_evidence_root : "",
|
|
458
475
|
acceptance_criteria: Array.isArray(body.acceptance_criteria)
|
|
@@ -537,6 +554,53 @@ export async function startAgent({
|
|
|
537
554
|
return;
|
|
538
555
|
}
|
|
539
556
|
|
|
557
|
+
const providerBudgetMatch = agenticLoopPath.match(
|
|
558
|
+
/^\/v1\/agentic-loop\/([^/]+)\/provider-token-budget$/,
|
|
559
|
+
);
|
|
560
|
+
if (req.method === "POST" && providerBudgetMatch) {
|
|
561
|
+
if (!acceptsNewWork(updateManager)) {
|
|
562
|
+
json(res, 503, updateInProgressResponse(updateManager));
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (!isSessionValid(session, Date.now(), apiOrigin)) {
|
|
566
|
+
json(res, 401, { error: "simy session expired; run simy again" });
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
const run = registry.get(decodeURIComponent(providerBudgetMatch[1]));
|
|
570
|
+
if (!run) {
|
|
571
|
+
json(res, 404, { error: "run not found" });
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
const body = await readJson(req);
|
|
575
|
+
let budget;
|
|
576
|
+
try {
|
|
577
|
+
budget = await increaseLocalCodingRunProviderTokenBudget(
|
|
578
|
+
run,
|
|
579
|
+
body.provider_token_budget,
|
|
580
|
+
);
|
|
581
|
+
} catch (error) {
|
|
582
|
+
json(res, 409, {
|
|
583
|
+
error: error instanceof Error ? error.message : "Provider token budget was rejected",
|
|
584
|
+
code: "provider_token_budget_rejected",
|
|
585
|
+
});
|
|
586
|
+
return;
|
|
587
|
+
}
|
|
588
|
+
const continuation = continueLocalCodingRun(
|
|
589
|
+
run,
|
|
590
|
+
String(body.message || "Continue after increasing the Provider token budget."),
|
|
591
|
+
runOptions,
|
|
592
|
+
);
|
|
593
|
+
json(res, 202, {
|
|
594
|
+
ok: true,
|
|
595
|
+
run_id: run.id,
|
|
596
|
+
state: "resuming",
|
|
597
|
+
provider_token_budget: budget.token_budget,
|
|
598
|
+
provider_tokens_used: budget.tokens_used,
|
|
599
|
+
});
|
|
600
|
+
void continuation.catch((error) => reportProviderBudgetResumeError(error, quiet));
|
|
601
|
+
return;
|
|
602
|
+
}
|
|
603
|
+
|
|
540
604
|
const guidanceMatch = agenticLoopPath.match(/^\/v1\/agentic-loop\/([^/]+)\/guidance$/);
|
|
541
605
|
if (req.method === "POST" && guidanceMatch) {
|
|
542
606
|
if (!isSessionValid(session, Date.now(), apiOrigin)) {
|
|
@@ -698,6 +762,9 @@ export async function startAgent({
|
|
|
698
762
|
loginUrl = new URL("/local-cli/connect", apiOrigin);
|
|
699
763
|
loginUrl.searchParams.set("port", String(port));
|
|
700
764
|
loginUrl.searchParams.set("nonce", authNonce);
|
|
765
|
+
loginUrl.searchParams.set("cli_version", CLI_VERSION);
|
|
766
|
+
loginUrl.searchParams.set("api_contract_version", String(CLI_API_CONTRACT_VERSION));
|
|
767
|
+
if (installMode) loginUrl.searchParams.set("install_mode", installMode);
|
|
701
768
|
if (!quiet) {
|
|
702
769
|
console.log(`Sign in to SIMY: ${loginUrl.toString()}`);
|
|
703
770
|
console.log(`Session file: ${sessionPath(apiOrigin, sessionRoot)}`);
|
|
@@ -910,6 +977,8 @@ export async function heartbeatLocalDevice({
|
|
|
910
977
|
),
|
|
911
978
|
body: JSON.stringify({
|
|
912
979
|
port,
|
|
980
|
+
cli_version: CLI_VERSION,
|
|
981
|
+
api_contract_version: CLI_API_CONTRACT_VERSION,
|
|
913
982
|
capabilities,
|
|
914
983
|
repo_inventory: repoInventory,
|
|
915
984
|
}),
|
|
@@ -943,6 +1012,14 @@ function reportRepositoryResumeError(error, quiet) {
|
|
|
943
1012
|
);
|
|
944
1013
|
}
|
|
945
1014
|
|
|
1015
|
+
function reportProviderBudgetResumeError(error, quiet) {
|
|
1016
|
+
if (!quiet) {
|
|
1017
|
+
console.error(
|
|
1018
|
+
`Provider token budget was increased, but the run could not resume: ${error instanceof Error ? error.message : error}`,
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
|
|
946
1023
|
function localRepositoryHilRequestId(run) {
|
|
947
1024
|
return `local-repository-scan:${run.id}`;
|
|
948
1025
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const CLI_VERSION = "0.2.1";
|
|
2
|
+
export const CLI_API_CONTRACT_VERSION = 2;
|
|
3
|
+
|
|
4
|
+
export function withCliContract(capabilities = {}, installMode = null) {
|
|
5
|
+
return {
|
|
6
|
+
...capabilities,
|
|
7
|
+
cli_version: CLI_VERSION,
|
|
8
|
+
api_contract_version: CLI_API_CONTRACT_VERSION,
|
|
9
|
+
...(installMode ? { install_mode: installMode } : {}),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -2,7 +2,8 @@ import { clampAttempts } from "./shared.js";
|
|
|
2
2
|
|
|
3
3
|
export const DEFAULT_TOKEN_BUDGET = 250_000;
|
|
4
4
|
export const DEFAULT_RETRY_BUDGET = 2;
|
|
5
|
-
const
|
|
5
|
+
export const MAX_PROVIDER_TOKEN_BUDGET = 10_000_000;
|
|
6
|
+
const PROVIDER_TOKEN_BUDGET_STEP = 250_000;
|
|
6
7
|
|
|
7
8
|
export function resolveRunBudgets(request = {}) {
|
|
8
9
|
const legacyMaxAttempts = clampAttempts(request.max_attempts);
|
|
@@ -12,12 +13,47 @@ export function resolveRunBudgets(request = {}) {
|
|
|
12
13
|
? legacyMaxAttempts - 1
|
|
13
14
|
: Math.min(4, Math.max(0, explicitRetryBudget));
|
|
14
15
|
return {
|
|
15
|
-
token_budget: normalizeTokenBudget(request.token_budget),
|
|
16
|
+
token_budget: normalizeTokenBudget(request.provider_token_budget ?? request.token_budget),
|
|
16
17
|
retry_budget: retryBudget,
|
|
17
18
|
max_attempts: retryBudget + 1,
|
|
18
19
|
};
|
|
19
20
|
}
|
|
20
21
|
|
|
22
|
+
export function suggestedProviderTokenBudget(value) {
|
|
23
|
+
const state = value?.schema_version === 1 ? value : budgetState(value);
|
|
24
|
+
if (state.tokens_used >= MAX_PROVIDER_TOKEN_BUDGET) return null;
|
|
25
|
+
const required = Math.max(
|
|
26
|
+
state.token_budget * 2,
|
|
27
|
+
state.tokens_used * 1.25,
|
|
28
|
+
state.tokens_used + PROVIDER_TOKEN_BUDGET_STEP,
|
|
29
|
+
);
|
|
30
|
+
return Math.min(
|
|
31
|
+
MAX_PROVIDER_TOKEN_BUDGET,
|
|
32
|
+
Math.ceil(required / PROVIDER_TOKEN_BUDGET_STEP) * PROVIDER_TOKEN_BUDGET_STEP,
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function increaseProviderTokenBudget(snapshot, value) {
|
|
37
|
+
const requested = integer(value);
|
|
38
|
+
if (requested === null || requested <= 0) {
|
|
39
|
+
throw new Error("Provider token budget must be a positive integer.");
|
|
40
|
+
}
|
|
41
|
+
if (requested > MAX_PROVIDER_TOKEN_BUDGET) {
|
|
42
|
+
throw new Error(
|
|
43
|
+
`Provider token budget cannot exceed ${MAX_PROVIDER_TOKEN_BUDGET.toLocaleString("en-US")}.`,
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
const current = budgetState(snapshot);
|
|
47
|
+
if (requested <= current.token_budget) {
|
|
48
|
+
throw new Error("Provider token budget must be higher than the current limit.");
|
|
49
|
+
}
|
|
50
|
+
if (requested <= current.tokens_used) {
|
|
51
|
+
throw new Error("Provider token budget must be higher than the Provider tokens already used.");
|
|
52
|
+
}
|
|
53
|
+
snapshot.charter.token_budget = requested;
|
|
54
|
+
return refreshBudgetState(snapshot);
|
|
55
|
+
}
|
|
56
|
+
|
|
21
57
|
export function budgetState(snapshot, pendingAttempt = null) {
|
|
22
58
|
const attempts = [
|
|
23
59
|
...(Array.isArray(snapshot?.attempts) ? snapshot.attempts : []),
|
|
@@ -93,7 +129,7 @@ function recordTokenTotal(value) {
|
|
|
93
129
|
function normalizeTokenBudget(value) {
|
|
94
130
|
const parsed = integer(value);
|
|
95
131
|
if (parsed === null || parsed <= 0) return DEFAULT_TOKEN_BUDGET;
|
|
96
|
-
return Math.min(
|
|
132
|
+
return Math.min(MAX_PROVIDER_TOKEN_BUDGET, parsed);
|
|
97
133
|
}
|
|
98
134
|
|
|
99
135
|
function normalizeRetryBudget(value, maxAttempts) {
|
package/src/orchestrator/loop.js
CHANGED
|
@@ -469,7 +469,7 @@ async function stopForBudget({ snapshot, attempt, reason, onUpdate }) {
|
|
|
469
469
|
if (attempt) attempt.budget = budget;
|
|
470
470
|
const message =
|
|
471
471
|
reason === "token_budget_exhausted"
|
|
472
|
-
? "Agentic loop
|
|
472
|
+
? "Agentic loop reached its Provider token budget."
|
|
473
473
|
: "Agentic loop exhausted its retry budget.";
|
|
474
474
|
appendEvent(snapshot, "blocked", message, {
|
|
475
475
|
code: reason,
|
|
@@ -487,8 +487,8 @@ function budgetFinding(reason, budget) {
|
|
|
487
487
|
passed: false,
|
|
488
488
|
code: "TOKEN_BUDGET_EXHAUSTED",
|
|
489
489
|
severity: "blocker",
|
|
490
|
-
target: "
|
|
491
|
-
explanation: `The run used ${budget.tokens_used} of ${budget.token_budget}
|
|
490
|
+
target: "provider_token_budget",
|
|
491
|
+
explanation: `The run used ${budget.tokens_used} of ${budget.token_budget} Provider tokens, so no additional executor or reviewer call can start until the Provider token budget is increased. Provider tokens are not billed as SIMY tokens.`,
|
|
492
492
|
repairability: "manual",
|
|
493
493
|
auto_fix_hint: null,
|
|
494
494
|
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { suggestedProviderTokenBudget } from "./budget.js";
|
|
2
|
+
|
|
1
3
|
const RECOVERABLE_STATES = new Set(["waiting_human", "blocked", "failed"]);
|
|
2
4
|
|
|
3
5
|
export function recoveryContractForEvent({ snapshot, state, message, detail = {} }) {
|
|
@@ -6,7 +8,7 @@ export function recoveryContractForEvent({ snapshot, state, message, detail = {}
|
|
|
6
8
|
|
|
7
9
|
const reasonCode = recoveryReasonCode(state, message, detail);
|
|
8
10
|
const attemptNumber = positiveInteger(detail.attempt_number);
|
|
9
|
-
const actions = recoveryActions(reasonCode);
|
|
11
|
+
const actions = recoveryActions(reasonCode, detail.budget);
|
|
10
12
|
return {
|
|
11
13
|
schema_version: 1,
|
|
12
14
|
state,
|
|
@@ -18,6 +20,8 @@ export function recoveryContractForEvent({ snapshot, state, message, detail = {}
|
|
|
18
20
|
event_code: clean(detail.code) || null,
|
|
19
21
|
attempt_number: attemptNumber,
|
|
20
22
|
max_attempts: positiveInteger(snapshot?.charter?.max_attempts),
|
|
23
|
+
provider_token_budget: positiveInteger(snapshot?.charter?.token_budget),
|
|
24
|
+
provider_tokens_used: nonNegativeInteger(detail?.budget?.tokens_used),
|
|
21
25
|
token_budget: positiveInteger(snapshot?.charter?.token_budget),
|
|
22
26
|
retry_budget: nonNegativeInteger(snapshot?.charter?.retry_budget),
|
|
23
27
|
tokens_used: nonNegativeInteger(detail?.budget?.tokens_used),
|
|
@@ -136,7 +140,7 @@ function recoveryMessage(reasonCode) {
|
|
|
136
140
|
"SIMY needs the missing requirement or approval before implementation can start.",
|
|
137
141
|
human_input_required: "SIMY needs your decision or clarification before it can continue.",
|
|
138
142
|
token_budget_exhausted:
|
|
139
|
-
"This run
|
|
143
|
+
"This run reached its Provider token safety limit. Provider tokens are not billed by SIMY. Increase the limit to continue this run.",
|
|
140
144
|
retry_budget_exhausted:
|
|
141
145
|
"This run used its automatic retry budget. Start a new run to continue with a fresh limit.",
|
|
142
146
|
automatic_progress_blocked:
|
|
@@ -149,7 +153,7 @@ function recoveryMessage(reasonCode) {
|
|
|
149
153
|
return messages[reasonCode] || messages.automatic_progress_blocked;
|
|
150
154
|
}
|
|
151
155
|
|
|
152
|
-
function recoveryActions(reasonCode) {
|
|
156
|
+
function recoveryActions(reasonCode, budget = null) {
|
|
153
157
|
if (reasonCode === "repository_authorization_required") {
|
|
154
158
|
return [
|
|
155
159
|
{
|
|
@@ -182,7 +186,25 @@ function recoveryActions(reasonCode) {
|
|
|
182
186
|
newRunAction(),
|
|
183
187
|
];
|
|
184
188
|
}
|
|
185
|
-
if (reasonCode === "token_budget_exhausted"
|
|
189
|
+
if (reasonCode === "token_budget_exhausted") {
|
|
190
|
+
const suggestedBudget = suggestedProviderTokenBudget(budget);
|
|
191
|
+
if (!suggestedBudget || budget?.retry_exhausted === true) return [newRunAction()];
|
|
192
|
+
return [
|
|
193
|
+
{
|
|
194
|
+
id: "increase_provider_token_budget",
|
|
195
|
+
type: "increase_provider_token_budget",
|
|
196
|
+
label: "Increase Provider token budget",
|
|
197
|
+
description:
|
|
198
|
+
"Raise this run's safety limit and continue. Provider tokens are usage diagnostics and are not billed as SIMY tokens.",
|
|
199
|
+
command: null,
|
|
200
|
+
payload: {
|
|
201
|
+
...(suggestedBudget ? { suggested_provider_token_budget: suggestedBudget } : {}),
|
|
202
|
+
},
|
|
203
|
+
},
|
|
204
|
+
newRunAction(),
|
|
205
|
+
];
|
|
206
|
+
}
|
|
207
|
+
if (reasonCode === "retry_budget_exhausted") {
|
|
186
208
|
return [newRunAction()];
|
|
187
209
|
}
|
|
188
210
|
if (reasonCode === "local_execution_interrupted") {
|
package/src/runner.js
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
redactExecutionText,
|
|
20
20
|
} from "./orchestrator/execution-io.js";
|
|
21
21
|
import { summarizeCodingLoopEvent } from "./orchestrator/presentation.js";
|
|
22
|
-
import { refreshBudgetState } from "./orchestrator/budget.js";
|
|
22
|
+
import { increaseProviderTokenBudget, refreshBudgetState } from "./orchestrator/budget.js";
|
|
23
23
|
import { appendEvent } from "./orchestrator/shared.js";
|
|
24
24
|
import {
|
|
25
25
|
createProviderStreamDecoder,
|
|
@@ -252,6 +252,35 @@ export async function continueLocalCodingRun(run, guidance, dependencies = {}) {
|
|
|
252
252
|
return executeLocalCodingRun(run, dependencies, { humanGuidance: message, resume: true });
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
+
export async function increaseLocalCodingRunProviderTokenBudget(run, value) {
|
|
256
|
+
if (run.operation || run.child) {
|
|
257
|
+
throw new Error("Provider token budget can change only while the run is waiting.");
|
|
258
|
+
}
|
|
259
|
+
if (!RESUMABLE_STATES.has(run.status)) {
|
|
260
|
+
throw new Error("Provider token budget can change only for a blocked or waiting run.");
|
|
261
|
+
}
|
|
262
|
+
const currentBudget = refreshBudgetState(run.snapshot);
|
|
263
|
+
if (currentBudget.retry_exhausted) {
|
|
264
|
+
throw new Error("The Agentic Loop run also used its retry budget. Start a new run to continue.");
|
|
265
|
+
}
|
|
266
|
+
const previousBudget = currentBudget.token_budget;
|
|
267
|
+
const budget = increaseProviderTokenBudget(run.snapshot, value);
|
|
268
|
+
run.request.token_budget = budget.token_budget;
|
|
269
|
+
appendEvent(
|
|
270
|
+
run.snapshot,
|
|
271
|
+
"waiting_human",
|
|
272
|
+
`Provider token budget increased from ${previousBudget} to ${budget.token_budget}.`,
|
|
273
|
+
{
|
|
274
|
+
code: "provider_token_budget_increased",
|
|
275
|
+
previous_provider_token_budget: previousBudget,
|
|
276
|
+
provider_token_budget: budget.token_budget,
|
|
277
|
+
budget,
|
|
278
|
+
},
|
|
279
|
+
);
|
|
280
|
+
await updateRun(run, "waiting_human");
|
|
281
|
+
return budget;
|
|
282
|
+
}
|
|
283
|
+
|
|
255
284
|
export async function continueLocalCodingRunAfterRepositoryApproval(
|
|
256
285
|
run,
|
|
257
286
|
{ repository, localPath } = {},
|