@odla-ai/cli 0.37.1 → 0.38.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/dist/bin.cjs +199 -62
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-XFIFFYNH.js → chunk-HETCZVFB.js} +183 -56
- package/dist/chunk-HETCZVFB.js.map +1 -0
- package/dist/{cli-OH6PCYZ3.js → cli-LGYDXY5R.js} +2 -2
- package/dist/index.cjs +182 -55
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-XFIFFYNH.js.map +0 -1
- /package/dist/{cli-OH6PCYZ3.js.map → cli-LGYDXY5R.js.map} +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
runCli
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-HETCZVFB.js";
|
|
5
5
|
import {
|
|
6
6
|
exitCodeFor
|
|
7
7
|
} from "./chunk-UKLSRQ5J.js";
|
|
@@ -9,4 +9,4 @@ export {
|
|
|
9
9
|
exitCodeFor,
|
|
10
10
|
runCli
|
|
11
11
|
};
|
|
12
|
-
//# sourceMappingURL=cli-
|
|
12
|
+
//# sourceMappingURL=cli-LGYDXY5R.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -1110,6 +1110,66 @@ function addOption(options, name, value2) {
|
|
|
1110
1110
|
else options[name] = [String(current), String(value2)];
|
|
1111
1111
|
}
|
|
1112
1112
|
|
|
1113
|
+
// src/admin-spend.ts
|
|
1114
|
+
async function call(ctx, method, scope) {
|
|
1115
|
+
const url = `${ctx.platformUrl.replace(/\/$/, "")}/registry/platform/spend?scope=${encodeURIComponent(scope)}`;
|
|
1116
|
+
const response2 = await ctx.doFetch(url, {
|
|
1117
|
+
method,
|
|
1118
|
+
headers: { authorization: `Bearer ${ctx.token}` }
|
|
1119
|
+
});
|
|
1120
|
+
const body = await response2.json().catch(() => ({}));
|
|
1121
|
+
if (!response2.ok) {
|
|
1122
|
+
const detail = body.error?.message ?? body.error?.code ?? `registry returned ${response2.status}`;
|
|
1123
|
+
throw new Error(`spend ${method} failed: ${detail} (${response2.status})`);
|
|
1124
|
+
}
|
|
1125
|
+
return body;
|
|
1126
|
+
}
|
|
1127
|
+
var money = (value2) => `$${value2.toFixed(2)}`;
|
|
1128
|
+
async function spendShow(ctx, scope) {
|
|
1129
|
+
const view = await call(ctx, "GET", scope);
|
|
1130
|
+
if (ctx.json) return ctx.out.log(JSON.stringify(view, null, 2));
|
|
1131
|
+
ctx.out.log(`scope: ${view.scope}`);
|
|
1132
|
+
ctx.out.log(`day: ${view.day} (UTC)`);
|
|
1133
|
+
ctx.out.log(
|
|
1134
|
+
`spent: ${money(view.spentUsd)} of ${view.capUsd > 0 ? money(view.capUsd) : "(no cap set)"}`
|
|
1135
|
+
);
|
|
1136
|
+
ctx.out.log(`calls: ${view.calls}`);
|
|
1137
|
+
if (view.unpricedCalls > 0) {
|
|
1138
|
+
ctx.out.log(
|
|
1139
|
+
`unpriced: ${view.unpricedCalls} call(s) had no price, so "spent" is a floor, not the total.`
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
if (!view.latched) return ctx.out.log("status: running");
|
|
1143
|
+
ctx.out.log(
|
|
1144
|
+
`status: HALTED \u2014 reached ${money(view.latched.capUsd)} on ${view.latched.day} (${view.latched.reason}), at ${money(view.latched.costUsd)}.`
|
|
1145
|
+
);
|
|
1146
|
+
ctx.out.log("A new day does not clear this. Resume with:");
|
|
1147
|
+
ctx.out.log(` odla-ai admin spend reset ${view.scope}`);
|
|
1148
|
+
}
|
|
1149
|
+
async function spendReset(ctx, scope) {
|
|
1150
|
+
const result = await call(ctx, "DELETE", scope);
|
|
1151
|
+
if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
|
|
1152
|
+
ctx.out.log(result.message);
|
|
1153
|
+
if (result.stillOverCap) {
|
|
1154
|
+
ctx.out.log(
|
|
1155
|
+
` today: ${money(result.spentUsd)} against a ${money(result.capUsd)} cap.`
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
async function adminSpend(parsed, ctx) {
|
|
1160
|
+
const action2 = parsed.positionals[2];
|
|
1161
|
+
const scope = parsed.positionals[3] ?? stringOpt(parsed.options.scope);
|
|
1162
|
+
if (action2 !== "show" && action2 !== "reset") {
|
|
1163
|
+
throw new Error('unknown spend command. Try "odla-ai admin spend show <scope>".');
|
|
1164
|
+
}
|
|
1165
|
+
if (!scope) {
|
|
1166
|
+
throw new Error(
|
|
1167
|
+
`"admin spend ${action2}" needs a scope, e.g. odla-ai admin spend ${action2} app:my-app:<incarnation>`
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
return action2 === "show" ? spendShow(ctx, scope) : spendReset(ctx, scope);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1113
1173
|
// src/operator-context.ts
|
|
1114
1174
|
var import_node_fs8 = require("fs");
|
|
1115
1175
|
var import_node_path7 = require("path");
|
|
@@ -1798,7 +1858,11 @@ async function resolveOperatorContext(parsed, options = {}) {
|
|
|
1798
1858
|
const appEnvironment = clean2(import_node_process10.default.env.ODLA_APP_ID);
|
|
1799
1859
|
const appValue = appFlag ?? appEnvironment ?? profile.value?.app ?? loaded?.app.id ?? null;
|
|
1800
1860
|
const appSource = appFlag ? "flag" : appEnvironment ? "environment" : profile.value?.app ? "profile" : loaded ? "config" : "unresolved";
|
|
1801
|
-
if (appValue)
|
|
1861
|
+
if (appValue) {
|
|
1862
|
+
for (const id2 of options.allowAppList ? appValue.split(",") : [appValue]) {
|
|
1863
|
+
assertOperatorName(id2.trim(), "app");
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1802
1866
|
if (options.requireApp && !appValue) {
|
|
1803
1867
|
throw new Error(
|
|
1804
1868
|
"app context is unresolved; pass --app <id>, set ODLA_APP_ID, select --context <name>, or run inside a project with odla.config.mjs"
|
|
@@ -1889,6 +1953,31 @@ var SET_OPTIONS = [
|
|
|
1889
1953
|
async function adminCommand(parsed, deps = {}) {
|
|
1890
1954
|
const area = parsed.positionals[1];
|
|
1891
1955
|
const action2 = parsed.positionals[2];
|
|
1956
|
+
if (area === "spend") {
|
|
1957
|
+
assertArgs(parsed, JSON_OPTIONS, 4);
|
|
1958
|
+
const context2 = await resolveOperatorContext(parsed, { allowMissingConfig: true });
|
|
1959
|
+
const out = deps.stdout ?? console;
|
|
1960
|
+
const doFetch = deps.fetch ?? fetch;
|
|
1961
|
+
const token = await getDeveloperToken(
|
|
1962
|
+
context2.cfg,
|
|
1963
|
+
{
|
|
1964
|
+
configPath: context2.cfg.configPath,
|
|
1965
|
+
token: stringOpt(parsed.options.token),
|
|
1966
|
+
email: stringOpt(parsed.options.email),
|
|
1967
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
1968
|
+
openApprovalUrl: deps.openUrl
|
|
1969
|
+
},
|
|
1970
|
+
doFetch,
|
|
1971
|
+
out
|
|
1972
|
+
);
|
|
1973
|
+
return adminSpend(parsed, {
|
|
1974
|
+
platformUrl: context2.platform.value,
|
|
1975
|
+
token,
|
|
1976
|
+
doFetch,
|
|
1977
|
+
json: parsed.options.json === true,
|
|
1978
|
+
out
|
|
1979
|
+
});
|
|
1980
|
+
}
|
|
1892
1981
|
const credentialSet = action2 === "credential" && parsed.positionals[3] === "set";
|
|
1893
1982
|
const credentials = action2 === "credentials";
|
|
1894
1983
|
const models = action2 === "models";
|
|
@@ -1992,7 +2081,8 @@ async function fetchIdentity(platformUrl, token, doFetch) {
|
|
|
1992
2081
|
email,
|
|
1993
2082
|
admin: body.admin === true,
|
|
1994
2083
|
machine,
|
|
1995
|
-
scopes
|
|
2084
|
+
scopes,
|
|
2085
|
+
projects: Array.isArray(body.projects) ? body.projects.map(String) : null
|
|
1996
2086
|
};
|
|
1997
2087
|
}
|
|
1998
2088
|
function credentialLabel(identity) {
|
|
@@ -2062,6 +2152,13 @@ async function whoamiCommand(parsed, deps = {}) {
|
|
|
2062
2152
|
out.log(`credential id: ${identity.credential.id}`);
|
|
2063
2153
|
out.log(`admin: ${identity.admin ? "yes" : "no"}`);
|
|
2064
2154
|
if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
|
|
2155
|
+
if (identity.projects === null) {
|
|
2156
|
+
out.log("projects: (this registry does not report project grants)");
|
|
2157
|
+
} else if (identity.projects.length) {
|
|
2158
|
+
out.log(`projects: ${identity.projects.join(", ")}`);
|
|
2159
|
+
} else {
|
|
2160
|
+
out.log("projects: (none \u2014 every pm and discuss call will be refused)");
|
|
2161
|
+
}
|
|
2065
2162
|
if (!identity.admin) {
|
|
2066
2163
|
if (identity.scopes.includes("platform:runbook:write")) {
|
|
2067
2164
|
out.log("\nThis exact scope can read and edit all platform runbook content.");
|
|
@@ -5607,10 +5704,10 @@ var import_node_fs16 = require("fs");
|
|
|
5607
5704
|
var import_node_os4 = require("os");
|
|
5608
5705
|
var import_node_path15 = require("path");
|
|
5609
5706
|
|
|
5610
|
-
// ../harness/dist/chunk-
|
|
5707
|
+
// ../harness/dist/chunk-LNQNFGQC.js
|
|
5611
5708
|
var HARNESS_PROTOCOL_VERSION = 1;
|
|
5612
5709
|
|
|
5613
|
-
// ../harness/dist/chunk-
|
|
5710
|
+
// ../harness/dist/chunk-K76I2TCQ.js
|
|
5614
5711
|
var import_child_process = require("child_process");
|
|
5615
5712
|
var import_fs = require("fs");
|
|
5616
5713
|
var import_promises2 = require("fs/promises");
|
|
@@ -5983,7 +6080,7 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
|
|
|
5983
6080
|
}
|
|
5984
6081
|
}
|
|
5985
6082
|
|
|
5986
|
-
// ../harness/dist/chunk-
|
|
6083
|
+
// ../harness/dist/chunk-UVGZHNLW.js
|
|
5987
6084
|
var import_crypto = require("crypto");
|
|
5988
6085
|
var import_promises5 = require("fs/promises");
|
|
5989
6086
|
var import_path5 = require("path");
|
|
@@ -6320,7 +6417,7 @@ function validateSnapshot(snapshot, limits) {
|
|
|
6320
6417
|
}
|
|
6321
6418
|
}
|
|
6322
6419
|
|
|
6323
|
-
// ../harness/dist/chunk-
|
|
6420
|
+
// ../harness/dist/chunk-UVGZHNLW.js
|
|
6324
6421
|
var import_child_process4 = require("child_process");
|
|
6325
6422
|
var import_promises6 = require("fs/promises");
|
|
6326
6423
|
var import_path6 = require("path");
|
|
@@ -6622,7 +6719,7 @@ function looksLikeDestination(value2) {
|
|
|
6622
6719
|
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text3) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text3);
|
|
6623
6720
|
}
|
|
6624
6721
|
|
|
6625
|
-
// ../harness/dist/chunk-
|
|
6722
|
+
// ../harness/dist/chunk-UVGZHNLW.js
|
|
6626
6723
|
var import_promises10 = require("fs/promises");
|
|
6627
6724
|
var import_promises11 = require("fs/promises");
|
|
6628
6725
|
var import_path10 = require("path");
|
|
@@ -6890,7 +6987,7 @@ async function buildCodeGraph(input) {
|
|
|
6890
6987
|
return builder.build();
|
|
6891
6988
|
}
|
|
6892
6989
|
|
|
6893
|
-
// ../harness/dist/chunk-
|
|
6990
|
+
// ../harness/dist/chunk-UVGZHNLW.js
|
|
6894
6991
|
var import_crypto4 = require("crypto");
|
|
6895
6992
|
async function digestStagedWorkspace(root, limits) {
|
|
6896
6993
|
const files = [];
|
|
@@ -7010,7 +7107,7 @@ function createCodeRuntimeControlClient(options) {
|
|
|
7010
7107
|
throw new TypeError("modelRequestTimeoutMs must be an integer from 30000 to 1800000");
|
|
7011
7108
|
}
|
|
7012
7109
|
const request3 = options.fetch ?? fetch;
|
|
7013
|
-
const
|
|
7110
|
+
const call4 = async (path, body, timeoutMs = requestTimeoutMs) => {
|
|
7014
7111
|
const timeout = AbortSignal.timeout(timeoutMs);
|
|
7015
7112
|
const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
|
|
7016
7113
|
let response2;
|
|
@@ -7040,17 +7137,17 @@ function createCodeRuntimeControlClient(options) {
|
|
|
7040
7137
|
return {
|
|
7041
7138
|
heartbeat: async (version, capabilities) => {
|
|
7042
7139
|
validateHeartbeat(version, capabilities);
|
|
7043
|
-
return parseSnapshot(await
|
|
7140
|
+
return parseSnapshot(await call4("/registry/code/runtime/heartbeat", { runtimeVersion: version, capabilities }));
|
|
7044
7141
|
},
|
|
7045
7142
|
acknowledge: async (commandId, result) => {
|
|
7046
7143
|
if (!/^ccmd_[0-9a-f]{32}$/.test(commandId)) throw new TypeError("invalid Code runtime command id");
|
|
7047
|
-
await
|
|
7144
|
+
await call4(`/registry/code/runtime/commands/${commandId}/ack`, result);
|
|
7048
7145
|
},
|
|
7049
7146
|
source: async (sessionId) => parseSource(
|
|
7050
|
-
await
|
|
7147
|
+
await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
|
|
7051
7148
|
),
|
|
7052
7149
|
infer: async (sessionId, inference) => {
|
|
7053
|
-
const value2 = record5(await
|
|
7150
|
+
const value2 = record5(await call4(
|
|
7054
7151
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
|
|
7055
7152
|
inference,
|
|
7056
7153
|
modelRequestTimeoutMs
|
|
@@ -7061,11 +7158,11 @@ function createCodeRuntimeControlClient(options) {
|
|
|
7061
7158
|
return value2;
|
|
7062
7159
|
},
|
|
7063
7160
|
review: async (sessionId, review) => parseReview(
|
|
7064
|
-
await
|
|
7161
|
+
await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review, modelRequestTimeoutMs)
|
|
7065
7162
|
),
|
|
7066
7163
|
submitCandidate: async (sessionId, checkpointId, verification) => {
|
|
7067
7164
|
if (!/^cpoint_[0-9a-f]{32}$/.test(checkpointId)) throw new TypeError("invalid Code checkpoint id");
|
|
7068
|
-
return parseCandidate(await
|
|
7165
|
+
return parseCandidate(await call4(
|
|
7069
7166
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/candidates`,
|
|
7070
7167
|
{ checkpointId, verification }
|
|
7071
7168
|
));
|
|
@@ -7075,21 +7172,21 @@ function createCodeRuntimeControlClient(options) {
|
|
|
7075
7172
|
if (!/^[A-Za-z0-9._:-]{1,120}$/.test(eventId) || !event || typeof event !== "object" || new TextEncoder().encode(serialized).byteLength > 24e3) {
|
|
7076
7173
|
throw new TypeError("invalid Code session event");
|
|
7077
7174
|
}
|
|
7078
|
-
await
|
|
7175
|
+
await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
|
|
7079
7176
|
},
|
|
7080
7177
|
recallMemories: async (sessionId, subjects, limit) => {
|
|
7081
|
-
const response2 = await
|
|
7178
|
+
const response2 = await call4(
|
|
7082
7179
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/recall`,
|
|
7083
7180
|
{ subjects: [...subjects], limit }
|
|
7084
7181
|
);
|
|
7085
7182
|
return Array.isArray(response2.memories) ? response2.memories : [];
|
|
7086
7183
|
},
|
|
7087
7184
|
rememberMemory: async (sessionId, memory) => {
|
|
7088
|
-
await
|
|
7185
|
+
await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/remember`, memory);
|
|
7089
7186
|
},
|
|
7090
7187
|
reportSessionFailure: async (sessionId, message2) => {
|
|
7091
7188
|
if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
|
|
7092
|
-
await
|
|
7189
|
+
await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
|
|
7093
7190
|
}
|
|
7094
7191
|
};
|
|
7095
7192
|
}
|
|
@@ -7978,7 +8075,7 @@ var SYSTEM_PROMPT_FOR = {
|
|
|
7978
8075
|
};
|
|
7979
8076
|
function codeSkill(opts) {
|
|
7980
8077
|
let seq = 0;
|
|
7981
|
-
const
|
|
8078
|
+
const call4 = async (tool, input, signal) => {
|
|
7982
8079
|
const startedAt = Date.now();
|
|
7983
8080
|
const response2 = await opts.broker.execute(
|
|
7984
8081
|
{ lease: opts.lease, workspaceDir: opts.workspaceDir, signal },
|
|
@@ -8000,7 +8097,7 @@ function codeSkill(opts) {
|
|
|
8000
8097
|
},
|
|
8001
8098
|
additionalProperties: false
|
|
8002
8099
|
},
|
|
8003
|
-
handler: (input, ctx) =>
|
|
8100
|
+
handler: (input, ctx) => call4("sandbox.read", input, ctx.signal)
|
|
8004
8101
|
};
|
|
8005
8102
|
const applyPatch = {
|
|
8006
8103
|
name: "odla_apply_git_diff",
|
|
@@ -8011,7 +8108,7 @@ function codeSkill(opts) {
|
|
|
8011
8108
|
properties: { patch: { type: "string", minLength: 1, maxLength: 262144 } },
|
|
8012
8109
|
additionalProperties: false
|
|
8013
8110
|
},
|
|
8014
|
-
handler: (input, ctx) =>
|
|
8111
|
+
handler: (input, ctx) => call4("sandbox.apply_patch", input, ctx.signal)
|
|
8015
8112
|
};
|
|
8016
8113
|
const runRecipe = {
|
|
8017
8114
|
name: "odla_run_recipe",
|
|
@@ -8022,7 +8119,7 @@ function codeSkill(opts) {
|
|
|
8022
8119
|
properties: { recipeId: { type: "string", minLength: 1, maxLength: 120, pattern: "^[a-zA-Z0-9._:-]+$" } },
|
|
8023
8120
|
additionalProperties: false
|
|
8024
8121
|
},
|
|
8025
|
-
handler: (input, ctx) =>
|
|
8122
|
+
handler: (input, ctx) => call4("sandbox.run_recipe", input, ctx.signal)
|
|
8026
8123
|
};
|
|
8027
8124
|
const listFiles2 = {
|
|
8028
8125
|
name: "odla_list",
|
|
@@ -8035,7 +8132,7 @@ function codeSkill(opts) {
|
|
|
8035
8132
|
},
|
|
8036
8133
|
additionalProperties: false
|
|
8037
8134
|
},
|
|
8038
|
-
handler: (input, ctx) =>
|
|
8135
|
+
handler: (input, ctx) => call4("sandbox.list", input, ctx.signal)
|
|
8039
8136
|
};
|
|
8040
8137
|
const searchFiles = {
|
|
8041
8138
|
name: "odla_search",
|
|
@@ -8051,7 +8148,7 @@ function codeSkill(opts) {
|
|
|
8051
8148
|
},
|
|
8052
8149
|
additionalProperties: false
|
|
8053
8150
|
},
|
|
8054
|
-
handler: (input, ctx) =>
|
|
8151
|
+
handler: (input, ctx) => call4("sandbox.search", input, ctx.signal)
|
|
8055
8152
|
};
|
|
8056
8153
|
const graphTool = (name, tool, description, required) => ({
|
|
8057
8154
|
name,
|
|
@@ -8062,7 +8159,7 @@ function codeSkill(opts) {
|
|
|
8062
8159
|
properties: { query: { type: "string", maxLength: 512 } },
|
|
8063
8160
|
additionalProperties: false
|
|
8064
8161
|
},
|
|
8065
|
-
handler: (input, ctx) =>
|
|
8162
|
+
handler: (input, ctx) => call4(tool, input, ctx.signal)
|
|
8066
8163
|
});
|
|
8067
8164
|
const orientation = [
|
|
8068
8165
|
graphTool(
|
|
@@ -8101,9 +8198,9 @@ async function runCodeAgent(options) {
|
|
|
8101
8198
|
lease: options.lease,
|
|
8102
8199
|
workspaceDir: options.workspaceDir,
|
|
8103
8200
|
surface,
|
|
8104
|
-
onToolCall: (
|
|
8105
|
-
toolCalls.push(
|
|
8106
|
-
options.onToolCall?.(
|
|
8201
|
+
onToolCall: (call4) => {
|
|
8202
|
+
toolCalls.push(call4);
|
|
8203
|
+
options.onToolCall?.(call4);
|
|
8107
8204
|
}
|
|
8108
8205
|
});
|
|
8109
8206
|
const compaction = options.compaction === void 0 ? (0, import_ai4.keepRecentExchanges)({ whenInputTokensExceed: 12e4, keep: 3 }) : options.compaction;
|
|
@@ -8188,6 +8285,9 @@ async function handleCodeRuntimeInference(input) {
|
|
|
8188
8285
|
call: request3.call
|
|
8189
8286
|
});
|
|
8190
8287
|
state2.tokens += response2.receipt.inputTokens + response2.receipt.outputTokens;
|
|
8288
|
+
const { costUsd } = response2.receipt;
|
|
8289
|
+
if (costUsd === void 0) state2.costKnown = false;
|
|
8290
|
+
else state2.costUsd += costUsd;
|
|
8191
8291
|
await input.event({
|
|
8192
8292
|
type: "usage",
|
|
8193
8293
|
provider: response2.receipt.provider,
|
|
@@ -8197,7 +8297,9 @@ async function handleCodeRuntimeInference(input) {
|
|
|
8197
8297
|
durationMs: Date.now() - startedAt,
|
|
8198
8298
|
interactionId: command.commandId,
|
|
8199
8299
|
interactionTokens: state2.tokens,
|
|
8200
|
-
interactionMaxTokens: metadata2.maxTokensPerInteraction
|
|
8300
|
+
interactionMaxTokens: metadata2.maxTokensPerInteraction,
|
|
8301
|
+
...costUsd === void 0 ? {} : { costUsd },
|
|
8302
|
+
...state2.costKnown ? { interactionCostUsd: state2.costUsd } : {}
|
|
8201
8303
|
}).catch(() => void 0);
|
|
8202
8304
|
return {
|
|
8203
8305
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
@@ -9041,10 +9143,16 @@ async function startGoalPursuit(input) {
|
|
|
9041
9143
|
attempt: async ({ prompt }) => {
|
|
9042
9144
|
const result = await input.attempt(prompt);
|
|
9043
9145
|
return {
|
|
9044
|
-
//
|
|
9045
|
-
//
|
|
9046
|
-
//
|
|
9047
|
-
|
|
9146
|
+
// What the attempt actually spent, so the runner's token_budget and
|
|
9147
|
+
// cost_budget checks can be reached. This used to be a hardcoded 0 with
|
|
9148
|
+
// no cost at all, which made maxTokens and maxUsd unreachable while
|
|
9149
|
+
// callers reasonably read them as hard ceilings.
|
|
9150
|
+
//
|
|
9151
|
+
// costUsd is omitted rather than zeroed when any call in the attempt
|
|
9152
|
+
// was unpriced: the runner only enforces a cost budget while the cost
|
|
9153
|
+
// is known, and a zero would make it enforce against a lie.
|
|
9154
|
+
tokens: result.tokens ?? 0,
|
|
9155
|
+
...result.costUsd === void 0 ? {} : { costUsd: result.costUsd },
|
|
9048
9156
|
...result.status === "failed" ? { error: result.error ?? "attempt failed" } : {}
|
|
9049
9157
|
};
|
|
9050
9158
|
},
|
|
@@ -9258,7 +9366,7 @@ var CodePiRuntimeEngine = class {
|
|
|
9258
9366
|
recipeAuthorization: this.options.recipeAuthorization
|
|
9259
9367
|
}, lease, metadata2.role));
|
|
9260
9368
|
const startedAt = Date.now();
|
|
9261
|
-
const interaction = { tokens: 0, noticeEmitted: false };
|
|
9369
|
+
const interaction = { tokens: 0, noticeEmitted: false, costUsd: 0, costKnown: true };
|
|
9262
9370
|
const inference = createCodeRuntimeInference({
|
|
9263
9371
|
command,
|
|
9264
9372
|
metadata: metadata2,
|
|
@@ -9296,7 +9404,11 @@ var CodePiRuntimeEngine = class {
|
|
|
9296
9404
|
await this.#diagnostic(command, active, detail);
|
|
9297
9405
|
await this.#failure(command, active, detail);
|
|
9298
9406
|
}
|
|
9299
|
-
return
|
|
9407
|
+
return {
|
|
9408
|
+
...result,
|
|
9409
|
+
tokens: interaction.tokens,
|
|
9410
|
+
...interaction.costKnown ? { costUsd: interaction.costUsd } : {}
|
|
9411
|
+
};
|
|
9300
9412
|
}
|
|
9301
9413
|
/** Report every brokered effect as it starts and finishes. */
|
|
9302
9414
|
#observed(command, active, broker) {
|
|
@@ -10405,6 +10517,8 @@ Usage:
|
|
|
10405
10517
|
odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
|
|
10406
10518
|
odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
|
|
10407
10519
|
odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
|
|
10520
|
+
odla-ai admin spend show <app:<id>:<incarnation>> [--context <name>] [--json]
|
|
10521
|
+
odla-ai admin spend reset <app:<id>:<incarnation>> [--context <name>] [--json]
|
|
10408
10522
|
odla-ai security github connect [--repo owner/name] [--env dev] [continue in Studio; human session required]
|
|
10409
10523
|
odla-ai security github disconnect --source <id> [--env dev] [continue in Studio; human session required]
|
|
10410
10524
|
odla-ai security plan [--env dev] [--json]
|
|
@@ -10497,6 +10611,9 @@ Commands:
|
|
|
10497
10611
|
human session connects one); "code grant request|list|approve|revoke" then
|
|
10498
10612
|
governs unattended access: an agent may request, only a human may approve.
|
|
10499
10613
|
admin Manage platform-funded AI routing/credentials/usage with narrow device grants.
|
|
10614
|
+
"admin spend" reads one app's daily inference spend and resumes it
|
|
10615
|
+
after a cap halts it. The cap is per UTC day; exhausting it LATCHES,
|
|
10616
|
+
and a new day does not clear the latch \u2014 resuming is deliberate.
|
|
10500
10617
|
security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
|
|
10501
10618
|
pm Project management (via @odla-ai/pm): Products contain Projects;
|
|
10502
10619
|
projects contain goals, kanban tasks, decisions, and bugs. Use
|
|
@@ -10504,6 +10621,12 @@ Commands:
|
|
|
10504
10621
|
pass --app/--project explicitly. Same device-grant auth as "app".
|
|
10505
10622
|
Status changes and comments post to each item's @odla-ai/chat
|
|
10506
10623
|
discussion thread.
|
|
10624
|
+
NOTE: "--column ready" is OWNER-ONLY. Creating a task in Ready
|
|
10625
|
+
approves its complete execution contract, so it needs pm.plan,
|
|
10626
|
+
which no device enrollment or handshake approval can grant. An
|
|
10627
|
+
agent proposes in Backlog (the default); a human owner or a
|
|
10628
|
+
pm.plan agent promotes. This is deliberate, not a permission
|
|
10629
|
+
gap \u2014 it was reported as one.
|
|
10507
10630
|
bug Intent-first alias for PM bugs. "bug report" writes to
|
|
10508
10631
|
odla PM; odla product defects do not belong in GitHub Issues.
|
|
10509
10632
|
discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
|
|
@@ -13618,7 +13741,11 @@ async function revoke(parsed, deps, cfg, doFetch, out, json) {
|
|
|
13618
13741
|
if (json) out.log(JSON.stringify({ deviceId, revoked: true }, null, 2));
|
|
13619
13742
|
}
|
|
13620
13743
|
async function scopedToken2(parsed, deps, cfg, doFetch, out, label, scope = "app:device:enroll") {
|
|
13621
|
-
const { credentials } = await resolveOperatorContext(parsed, {
|
|
13744
|
+
const { credentials } = await resolveOperatorContext(parsed, {
|
|
13745
|
+
allowMissingConfig: true,
|
|
13746
|
+
// A device is granted the apps named in ONE approval, so --app is a list here.
|
|
13747
|
+
allowAppList: true
|
|
13748
|
+
});
|
|
13622
13749
|
const scopedTokenFile = credentials.scopedTokenFile;
|
|
13623
13750
|
return getScopedPlatformToken({
|
|
13624
13751
|
platform: cfg.platformUrl,
|
|
@@ -13690,7 +13817,7 @@ function describeUnmet(slug, unmet) {
|
|
|
13690
13817
|
|
|
13691
13818
|
// src/runbook-actions.ts
|
|
13692
13819
|
var PLATFORM_SCOPE = "$platform";
|
|
13693
|
-
async function
|
|
13820
|
+
async function call2(ctx, method, path, body) {
|
|
13694
13821
|
const res = await ctx.doFetch(`${ctx.platformUrl.replace(/\/$/, "")}/registry/pm${path}`, {
|
|
13695
13822
|
method,
|
|
13696
13823
|
headers: {
|
|
@@ -13712,7 +13839,7 @@ async function call(ctx, method, path, body) {
|
|
|
13712
13839
|
throw new Error(message2);
|
|
13713
13840
|
}
|
|
13714
13841
|
async function bySlug(ctx, slug) {
|
|
13715
|
-
const page2 = await
|
|
13842
|
+
const page2 = await call2(
|
|
13716
13843
|
ctx,
|
|
13717
13844
|
"GET",
|
|
13718
13845
|
`/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(slug)}&limit=1`
|
|
@@ -13721,7 +13848,7 @@ async function bySlug(ctx, slug) {
|
|
|
13721
13848
|
if (filtered) return filtered;
|
|
13722
13849
|
const limit = 100;
|
|
13723
13850
|
for (let offset = 0; ; offset += limit) {
|
|
13724
|
-
const fallback = await
|
|
13851
|
+
const fallback = await call2(
|
|
13725
13852
|
ctx,
|
|
13726
13853
|
"GET",
|
|
13727
13854
|
`/runbook?app=${encodeURIComponent(ctx.appId)}&limit=${limit}&offset=${offset}`
|
|
@@ -13742,7 +13869,7 @@ async function runbookList(ctx, all, query) {
|
|
|
13742
13869
|
const params = new URLSearchParams();
|
|
13743
13870
|
if (!all) params.set("app", ctx.appId);
|
|
13744
13871
|
if (query) params.set("q", query);
|
|
13745
|
-
const page2 = await
|
|
13872
|
+
const page2 = await call2(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
|
|
13746
13873
|
if (ctx.json) return ctx.out.log(JSON.stringify(page2, null, 2));
|
|
13747
13874
|
if (!page2.records.length) return ctx.out.log("(no runbooks)");
|
|
13748
13875
|
ctx.out.log(["SLUG", "STATUS", "V", "SCOPE", "UPDATED", "TITLE"].join(" "));
|
|
@@ -13759,7 +13886,7 @@ async function runbookGet(ctx, slug) {
|
|
|
13759
13886
|
ctx.out.log(runbook.body);
|
|
13760
13887
|
}
|
|
13761
13888
|
async function runbookNew(ctx, slug, title, body, summary, requires) {
|
|
13762
|
-
const created = await
|
|
13889
|
+
const created = await call2(ctx, "POST", "/runbook", {
|
|
13763
13890
|
appId: ctx.appId,
|
|
13764
13891
|
input: { slug, title, body, ...summary ? { summary } : {}, ...requires ? { requires } : {} }
|
|
13765
13892
|
});
|
|
@@ -13767,7 +13894,7 @@ async function runbookNew(ctx, slug, title, body, summary, requires) {
|
|
|
13767
13894
|
}
|
|
13768
13895
|
async function runbookEdit(ctx, slug, body, note, requires, expectedVersion) {
|
|
13769
13896
|
const runbook = await bySlug(ctx, slug);
|
|
13770
|
-
const result = await
|
|
13897
|
+
const result = await call2(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
|
|
13771
13898
|
// An empty --requires clears the declaration; omitting the flag leaves
|
|
13772
13899
|
// whatever is there, so an ordinary body edit never drops it.
|
|
13773
13900
|
patch: {
|
|
@@ -13782,7 +13909,7 @@ async function runbookEdit(ctx, slug, body, note, requires, expectedVersion) {
|
|
|
13782
13909
|
}
|
|
13783
13910
|
async function runbookStatus(ctx, slug, status) {
|
|
13784
13911
|
const runbook = await bySlug(ctx, slug);
|
|
13785
|
-
const result = await
|
|
13912
|
+
const result = await call2(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
|
|
13786
13913
|
patch: { status }
|
|
13787
13914
|
});
|
|
13788
13915
|
ctx.out.log(ctx.json ? JSON.stringify(result, null, 2) : `${slug} \u2192 ${status}`);
|
|
@@ -13791,7 +13918,7 @@ async function runbookVisibility(ctx, slug, visibility) {
|
|
|
13791
13918
|
if (visibility !== "operator" && visibility !== "admin")
|
|
13792
13919
|
throw new Error(`visibility must be "operator" or "admin", got "${visibility}"`);
|
|
13793
13920
|
const runbook = await bySlug(ctx, slug);
|
|
13794
|
-
const result = await
|
|
13921
|
+
const result = await call2(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
|
|
13795
13922
|
patch: { visibility }
|
|
13796
13923
|
});
|
|
13797
13924
|
if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
|
|
@@ -13801,7 +13928,7 @@ async function runbookVisibility(ctx, slug, visibility) {
|
|
|
13801
13928
|
}
|
|
13802
13929
|
async function runbookHistory(ctx, slug) {
|
|
13803
13930
|
const runbook = await bySlug(ctx, slug);
|
|
13804
|
-
const page2 = await
|
|
13931
|
+
const page2 = await call2(ctx, "GET", `/runbook/${encodeURIComponent(runbook.id)}/revisions`);
|
|
13805
13932
|
if (ctx.json) return ctx.out.log(JSON.stringify(page2, null, 2));
|
|
13806
13933
|
ctx.out.log(`${slug} is at v${runbook.version}`);
|
|
13807
13934
|
if (!page2.records.length) return ctx.out.log("(no earlier versions)");
|
|
@@ -13811,7 +13938,7 @@ async function runbookHistory(ctx, slug) {
|
|
|
13811
13938
|
}
|
|
13812
13939
|
async function runbookRevert(ctx, slug, version) {
|
|
13813
13940
|
const runbook = await bySlug(ctx, slug);
|
|
13814
|
-
const result = await
|
|
13941
|
+
const result = await call2(
|
|
13815
13942
|
ctx,
|
|
13816
13943
|
"POST",
|
|
13817
13944
|
`/runbook/${encodeURIComponent(runbook.id)}/revert`,
|
|
@@ -13822,7 +13949,7 @@ async function runbookRevert(ctx, slug, version) {
|
|
|
13822
13949
|
}
|
|
13823
13950
|
async function runbookRemove(ctx, slug) {
|
|
13824
13951
|
const runbook = await bySlug(ctx, slug);
|
|
13825
|
-
await
|
|
13952
|
+
await call2(ctx, "DELETE", `/runbook/${encodeURIComponent(runbook.id)}`);
|
|
13826
13953
|
ctx.out.log(`removed ${slug}`);
|
|
13827
13954
|
}
|
|
13828
13955
|
|
|
@@ -13883,7 +14010,7 @@ ${counts.created} created, ${counts.updated} updated, ${counts.unchanged} unchan
|
|
|
13883
14010
|
async function upsert(ctx, r, visibility) {
|
|
13884
14011
|
const found = await bySlug(ctx, r.slug).catch(() => null);
|
|
13885
14012
|
if (!found) {
|
|
13886
|
-
await
|
|
14013
|
+
await call2(ctx, "POST", "/runbook", {
|
|
13887
14014
|
appId: ctx.appId,
|
|
13888
14015
|
input: {
|
|
13889
14016
|
slug: r.slug,
|
|
@@ -13902,7 +14029,7 @@ async function upsert(ctx, r, visibility) {
|
|
|
13902
14029
|
ctx.out.log(` = ${r.slug} (already current at v${found.version})`);
|
|
13903
14030
|
return "unchanged";
|
|
13904
14031
|
}
|
|
13905
|
-
const result = await
|
|
14032
|
+
const result = await call2(
|
|
13906
14033
|
ctx,
|
|
13907
14034
|
"PATCH",
|
|
13908
14035
|
`/runbook/${encodeURIComponent(found.id)}`,
|
|
@@ -14130,7 +14257,7 @@ async function assessImpact(ctx, surfaces, all, limit) {
|
|
|
14130
14257
|
for (const surface of surfaces) {
|
|
14131
14258
|
const params = new URLSearchParams({ q: surface.query, limit: String(limit) });
|
|
14132
14259
|
if (!all) params.set("app", ctx.appId);
|
|
14133
|
-
const result = await
|
|
14260
|
+
const result = await call2(ctx, "GET", `/runbook/search?${params}`);
|
|
14134
14261
|
const runbooks = result.outcome === "ranked" ? foldHits(result.hits) : result.candidates.map((c) => ({ ...c, version: 0, sections: [] }));
|
|
14135
14262
|
out.push({ surface, runbooks });
|
|
14136
14263
|
}
|
|
@@ -14213,7 +14340,7 @@ function lintRunbook(runbook, installed) {
|
|
|
14213
14340
|
async function runbookLint(ctx, all) {
|
|
14214
14341
|
const params = new URLSearchParams();
|
|
14215
14342
|
if (!all) params.set("app", ctx.appId);
|
|
14216
|
-
const page2 = await
|
|
14343
|
+
const page2 = await call2(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
|
|
14217
14344
|
const installed = { "@odla-ai/cli": cliVersion() };
|
|
14218
14345
|
const findings = page2.records.flatMap((runbook) => lintRunbook(runbook, installed));
|
|
14219
14346
|
if (ctx.json) return ctx.out.log(JSON.stringify({ checked: page2.records.length, findings }, null, 2));
|
|
@@ -14238,7 +14365,7 @@ async function runbookSearch(ctx, query, all, limit) {
|
|
|
14238
14365
|
const params = new URLSearchParams({ q: query });
|
|
14239
14366
|
if (!all) params.set("app", ctx.appId);
|
|
14240
14367
|
if (limit) params.set("limit", String(limit));
|
|
14241
|
-
const result = await
|
|
14368
|
+
const result = await call2(ctx, "GET", `/runbook/search?${params}`);
|
|
14242
14369
|
if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
|
|
14243
14370
|
if (result.outcome === "empty-corpus") return ctx.out.log("no runbooks are available to search");
|
|
14244
14371
|
if (result.outcome === "no-match") return ctx.out.log(`no runbook mentions "${query}"`);
|
|
@@ -14257,7 +14384,7 @@ async function runbookSearch(ctx, query, all, limit) {
|
|
|
14257
14384
|
}
|
|
14258
14385
|
var JSDOC_POINTER = "Runbooks are procedure. For what an export does \u2014 arguments, defaults, errors \u2014 read its JSDoc: the installed package's .d.ts, or its page under https://odla.ai/docs.";
|
|
14259
14386
|
async function runbookAsk(ctx, question, all) {
|
|
14260
|
-
const result = await
|
|
14387
|
+
const result = await call2(ctx, "POST", "/runbook/ask", {
|
|
14261
14388
|
question,
|
|
14262
14389
|
...all ? {} : { app: ctx.appId }
|
|
14263
14390
|
});
|
|
@@ -14287,7 +14414,7 @@ async function runbookAsk(ctx, question, all) {
|
|
|
14287
14414
|
}
|
|
14288
14415
|
async function runbookComment(ctx, slug, body) {
|
|
14289
14416
|
const found = await bySlug(ctx, slug);
|
|
14290
|
-
await
|
|
14417
|
+
await call2(ctx, "POST", `/runbook/${encodeURIComponent(found.id)}/comments`, { body });
|
|
14291
14418
|
ctx.out.log(`commented on ${slug} (v${found.version})`);
|
|
14292
14419
|
}
|
|
14293
14420
|
|