@odla-ai/cli 0.37.0 → 0.38.0
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 +202 -88
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-OWTIDSL5.js → chunk-DKOJBWRJ.js} +186 -82
- package/dist/chunk-DKOJBWRJ.js.map +1 -0
- package/dist/{cli-QR44X5IB.js → cli-DZKEPGBZ.js} +2 -2
- package/dist/index.cjs +185 -81
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
- package/dist/chunk-OWTIDSL5.js.map +0 -1
- /package/dist/{cli-QR44X5IB.js.map → cli-DZKEPGBZ.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-DKOJBWRJ.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-DZKEPGBZ.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");
|
|
@@ -1889,6 +1949,31 @@ var SET_OPTIONS = [
|
|
|
1889
1949
|
async function adminCommand(parsed, deps = {}) {
|
|
1890
1950
|
const area = parsed.positionals[1];
|
|
1891
1951
|
const action2 = parsed.positionals[2];
|
|
1952
|
+
if (area === "spend") {
|
|
1953
|
+
assertArgs(parsed, JSON_OPTIONS, 4);
|
|
1954
|
+
const context2 = await resolveOperatorContext(parsed, { allowMissingConfig: true });
|
|
1955
|
+
const out = deps.stdout ?? console;
|
|
1956
|
+
const doFetch = deps.fetch ?? fetch;
|
|
1957
|
+
const token = await getDeveloperToken(
|
|
1958
|
+
context2.cfg,
|
|
1959
|
+
{
|
|
1960
|
+
configPath: context2.cfg.configPath,
|
|
1961
|
+
token: stringOpt(parsed.options.token),
|
|
1962
|
+
email: stringOpt(parsed.options.email),
|
|
1963
|
+
open: parsed.options.open === false ? false : parsed.options.open === true ? true : void 0,
|
|
1964
|
+
openApprovalUrl: deps.openUrl
|
|
1965
|
+
},
|
|
1966
|
+
doFetch,
|
|
1967
|
+
out
|
|
1968
|
+
);
|
|
1969
|
+
return adminSpend(parsed, {
|
|
1970
|
+
platformUrl: context2.platform.value,
|
|
1971
|
+
token,
|
|
1972
|
+
doFetch,
|
|
1973
|
+
json: parsed.options.json === true,
|
|
1974
|
+
out
|
|
1975
|
+
});
|
|
1976
|
+
}
|
|
1892
1977
|
const credentialSet = action2 === "credential" && parsed.positionals[3] === "set";
|
|
1893
1978
|
const credentials = action2 === "credentials";
|
|
1894
1979
|
const models = action2 === "models";
|
|
@@ -1992,7 +2077,8 @@ async function fetchIdentity(platformUrl, token, doFetch) {
|
|
|
1992
2077
|
email,
|
|
1993
2078
|
admin: body.admin === true,
|
|
1994
2079
|
machine,
|
|
1995
|
-
scopes
|
|
2080
|
+
scopes,
|
|
2081
|
+
projects: Array.isArray(body.projects) ? body.projects.map(String) : null
|
|
1996
2082
|
};
|
|
1997
2083
|
}
|
|
1998
2084
|
function credentialLabel(identity) {
|
|
@@ -2062,6 +2148,13 @@ async function whoamiCommand(parsed, deps = {}) {
|
|
|
2062
2148
|
out.log(`credential id: ${identity.credential.id}`);
|
|
2063
2149
|
out.log(`admin: ${identity.admin ? "yes" : "no"}`);
|
|
2064
2150
|
if (identity.scopes.length) out.log(`scopes: ${identity.scopes.join(", ")}`);
|
|
2151
|
+
if (identity.projects === null) {
|
|
2152
|
+
out.log("projects: (this registry does not report project grants)");
|
|
2153
|
+
} else if (identity.projects.length) {
|
|
2154
|
+
out.log(`projects: ${identity.projects.join(", ")}`);
|
|
2155
|
+
} else {
|
|
2156
|
+
out.log("projects: (none \u2014 every pm and discuss call will be refused)");
|
|
2157
|
+
}
|
|
2065
2158
|
if (!identity.admin) {
|
|
2066
2159
|
if (identity.scopes.includes("platform:runbook:write")) {
|
|
2067
2160
|
out.log("\nThis exact scope can read and edit all platform runbook content.");
|
|
@@ -5607,10 +5700,10 @@ var import_node_fs16 = require("fs");
|
|
|
5607
5700
|
var import_node_os4 = require("os");
|
|
5608
5701
|
var import_node_path15 = require("path");
|
|
5609
5702
|
|
|
5610
|
-
// ../harness/dist/chunk-
|
|
5703
|
+
// ../harness/dist/chunk-LNQNFGQC.js
|
|
5611
5704
|
var HARNESS_PROTOCOL_VERSION = 1;
|
|
5612
5705
|
|
|
5613
|
-
// ../harness/dist/chunk-
|
|
5706
|
+
// ../harness/dist/chunk-K76I2TCQ.js
|
|
5614
5707
|
var import_child_process = require("child_process");
|
|
5615
5708
|
var import_fs = require("fs");
|
|
5616
5709
|
var import_promises2 = require("fs/promises");
|
|
@@ -5983,7 +6076,7 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
|
|
|
5983
6076
|
}
|
|
5984
6077
|
}
|
|
5985
6078
|
|
|
5986
|
-
// ../harness/dist/chunk-
|
|
6079
|
+
// ../harness/dist/chunk-UVGZHNLW.js
|
|
5987
6080
|
var import_crypto = require("crypto");
|
|
5988
6081
|
var import_promises5 = require("fs/promises");
|
|
5989
6082
|
var import_path5 = require("path");
|
|
@@ -6320,7 +6413,7 @@ function validateSnapshot(snapshot, limits) {
|
|
|
6320
6413
|
}
|
|
6321
6414
|
}
|
|
6322
6415
|
|
|
6323
|
-
// ../harness/dist/chunk-
|
|
6416
|
+
// ../harness/dist/chunk-UVGZHNLW.js
|
|
6324
6417
|
var import_child_process4 = require("child_process");
|
|
6325
6418
|
var import_promises6 = require("fs/promises");
|
|
6326
6419
|
var import_path6 = require("path");
|
|
@@ -6622,7 +6715,7 @@ function looksLikeDestination(value2) {
|
|
|
6622
6715
|
return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text3) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text3);
|
|
6623
6716
|
}
|
|
6624
6717
|
|
|
6625
|
-
// ../harness/dist/chunk-
|
|
6718
|
+
// ../harness/dist/chunk-UVGZHNLW.js
|
|
6626
6719
|
var import_promises10 = require("fs/promises");
|
|
6627
6720
|
var import_promises11 = require("fs/promises");
|
|
6628
6721
|
var import_path10 = require("path");
|
|
@@ -6890,7 +6983,7 @@ async function buildCodeGraph(input) {
|
|
|
6890
6983
|
return builder.build();
|
|
6891
6984
|
}
|
|
6892
6985
|
|
|
6893
|
-
// ../harness/dist/chunk-
|
|
6986
|
+
// ../harness/dist/chunk-UVGZHNLW.js
|
|
6894
6987
|
var import_crypto4 = require("crypto");
|
|
6895
6988
|
async function digestStagedWorkspace(root, limits) {
|
|
6896
6989
|
const files = [];
|
|
@@ -7010,7 +7103,7 @@ function createCodeRuntimeControlClient(options) {
|
|
|
7010
7103
|
throw new TypeError("modelRequestTimeoutMs must be an integer from 30000 to 1800000");
|
|
7011
7104
|
}
|
|
7012
7105
|
const request3 = options.fetch ?? fetch;
|
|
7013
|
-
const
|
|
7106
|
+
const call4 = async (path, body, timeoutMs = requestTimeoutMs) => {
|
|
7014
7107
|
const timeout = AbortSignal.timeout(timeoutMs);
|
|
7015
7108
|
const signal = options.signal ? AbortSignal.any([options.signal, timeout]) : timeout;
|
|
7016
7109
|
let response2;
|
|
@@ -7040,17 +7133,17 @@ function createCodeRuntimeControlClient(options) {
|
|
|
7040
7133
|
return {
|
|
7041
7134
|
heartbeat: async (version, capabilities) => {
|
|
7042
7135
|
validateHeartbeat(version, capabilities);
|
|
7043
|
-
return parseSnapshot(await
|
|
7136
|
+
return parseSnapshot(await call4("/registry/code/runtime/heartbeat", { runtimeVersion: version, capabilities }));
|
|
7044
7137
|
},
|
|
7045
7138
|
acknowledge: async (commandId, result) => {
|
|
7046
7139
|
if (!/^ccmd_[0-9a-f]{32}$/.test(commandId)) throw new TypeError("invalid Code runtime command id");
|
|
7047
|
-
await
|
|
7140
|
+
await call4(`/registry/code/runtime/commands/${commandId}/ack`, result);
|
|
7048
7141
|
},
|
|
7049
7142
|
source: async (sessionId) => parseSource(
|
|
7050
|
-
await
|
|
7143
|
+
await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/source`, {})
|
|
7051
7144
|
),
|
|
7052
7145
|
infer: async (sessionId, inference) => {
|
|
7053
|
-
const value2 = record5(await
|
|
7146
|
+
const value2 = record5(await call4(
|
|
7054
7147
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/inference`,
|
|
7055
7148
|
inference,
|
|
7056
7149
|
modelRequestTimeoutMs
|
|
@@ -7061,11 +7154,11 @@ function createCodeRuntimeControlClient(options) {
|
|
|
7061
7154
|
return value2;
|
|
7062
7155
|
},
|
|
7063
7156
|
review: async (sessionId, review) => parseReview(
|
|
7064
|
-
await
|
|
7157
|
+
await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/review`, review, modelRequestTimeoutMs)
|
|
7065
7158
|
),
|
|
7066
7159
|
submitCandidate: async (sessionId, checkpointId, verification) => {
|
|
7067
7160
|
if (!/^cpoint_[0-9a-f]{32}$/.test(checkpointId)) throw new TypeError("invalid Code checkpoint id");
|
|
7068
|
-
return parseCandidate(await
|
|
7161
|
+
return parseCandidate(await call4(
|
|
7069
7162
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/candidates`,
|
|
7070
7163
|
{ checkpointId, verification }
|
|
7071
7164
|
));
|
|
@@ -7075,21 +7168,21 @@ function createCodeRuntimeControlClient(options) {
|
|
|
7075
7168
|
if (!/^[A-Za-z0-9._:-]{1,120}$/.test(eventId) || !event || typeof event !== "object" || new TextEncoder().encode(serialized).byteLength > 24e3) {
|
|
7076
7169
|
throw new TypeError("invalid Code session event");
|
|
7077
7170
|
}
|
|
7078
|
-
await
|
|
7171
|
+
await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/chat/events`, { eventId, event });
|
|
7079
7172
|
},
|
|
7080
7173
|
recallMemories: async (sessionId, subjects, limit) => {
|
|
7081
|
-
const response2 = await
|
|
7174
|
+
const response2 = await call4(
|
|
7082
7175
|
`/registry/code/runtime/sessions/${validSessionId(sessionId)}/recall`,
|
|
7083
7176
|
{ subjects: [...subjects], limit }
|
|
7084
7177
|
);
|
|
7085
7178
|
return Array.isArray(response2.memories) ? response2.memories : [];
|
|
7086
7179
|
},
|
|
7087
7180
|
rememberMemory: async (sessionId, memory) => {
|
|
7088
|
-
await
|
|
7181
|
+
await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/remember`, memory);
|
|
7089
7182
|
},
|
|
7090
7183
|
reportSessionFailure: async (sessionId, message2) => {
|
|
7091
7184
|
if (!message2.trim() || message2.length > 2e3) throw new TypeError("invalid Code session failure");
|
|
7092
|
-
await
|
|
7185
|
+
await call4(`/registry/code/runtime/sessions/${validSessionId(sessionId)}/failure`, { message: message2 });
|
|
7093
7186
|
}
|
|
7094
7187
|
};
|
|
7095
7188
|
}
|
|
@@ -7978,7 +8071,7 @@ var SYSTEM_PROMPT_FOR = {
|
|
|
7978
8071
|
};
|
|
7979
8072
|
function codeSkill(opts) {
|
|
7980
8073
|
let seq = 0;
|
|
7981
|
-
const
|
|
8074
|
+
const call4 = async (tool, input, signal) => {
|
|
7982
8075
|
const startedAt = Date.now();
|
|
7983
8076
|
const response2 = await opts.broker.execute(
|
|
7984
8077
|
{ lease: opts.lease, workspaceDir: opts.workspaceDir, signal },
|
|
@@ -8000,7 +8093,7 @@ function codeSkill(opts) {
|
|
|
8000
8093
|
},
|
|
8001
8094
|
additionalProperties: false
|
|
8002
8095
|
},
|
|
8003
|
-
handler: (input, ctx) =>
|
|
8096
|
+
handler: (input, ctx) => call4("sandbox.read", input, ctx.signal)
|
|
8004
8097
|
};
|
|
8005
8098
|
const applyPatch = {
|
|
8006
8099
|
name: "odla_apply_git_diff",
|
|
@@ -8011,7 +8104,7 @@ function codeSkill(opts) {
|
|
|
8011
8104
|
properties: { patch: { type: "string", minLength: 1, maxLength: 262144 } },
|
|
8012
8105
|
additionalProperties: false
|
|
8013
8106
|
},
|
|
8014
|
-
handler: (input, ctx) =>
|
|
8107
|
+
handler: (input, ctx) => call4("sandbox.apply_patch", input, ctx.signal)
|
|
8015
8108
|
};
|
|
8016
8109
|
const runRecipe = {
|
|
8017
8110
|
name: "odla_run_recipe",
|
|
@@ -8022,7 +8115,7 @@ function codeSkill(opts) {
|
|
|
8022
8115
|
properties: { recipeId: { type: "string", minLength: 1, maxLength: 120, pattern: "^[a-zA-Z0-9._:-]+$" } },
|
|
8023
8116
|
additionalProperties: false
|
|
8024
8117
|
},
|
|
8025
|
-
handler: (input, ctx) =>
|
|
8118
|
+
handler: (input, ctx) => call4("sandbox.run_recipe", input, ctx.signal)
|
|
8026
8119
|
};
|
|
8027
8120
|
const listFiles2 = {
|
|
8028
8121
|
name: "odla_list",
|
|
@@ -8035,7 +8128,7 @@ function codeSkill(opts) {
|
|
|
8035
8128
|
},
|
|
8036
8129
|
additionalProperties: false
|
|
8037
8130
|
},
|
|
8038
|
-
handler: (input, ctx) =>
|
|
8131
|
+
handler: (input, ctx) => call4("sandbox.list", input, ctx.signal)
|
|
8039
8132
|
};
|
|
8040
8133
|
const searchFiles = {
|
|
8041
8134
|
name: "odla_search",
|
|
@@ -8051,7 +8144,7 @@ function codeSkill(opts) {
|
|
|
8051
8144
|
},
|
|
8052
8145
|
additionalProperties: false
|
|
8053
8146
|
},
|
|
8054
|
-
handler: (input, ctx) =>
|
|
8147
|
+
handler: (input, ctx) => call4("sandbox.search", input, ctx.signal)
|
|
8055
8148
|
};
|
|
8056
8149
|
const graphTool = (name, tool, description, required) => ({
|
|
8057
8150
|
name,
|
|
@@ -8062,7 +8155,7 @@ function codeSkill(opts) {
|
|
|
8062
8155
|
properties: { query: { type: "string", maxLength: 512 } },
|
|
8063
8156
|
additionalProperties: false
|
|
8064
8157
|
},
|
|
8065
|
-
handler: (input, ctx) =>
|
|
8158
|
+
handler: (input, ctx) => call4(tool, input, ctx.signal)
|
|
8066
8159
|
});
|
|
8067
8160
|
const orientation = [
|
|
8068
8161
|
graphTool(
|
|
@@ -8101,9 +8194,9 @@ async function runCodeAgent(options) {
|
|
|
8101
8194
|
lease: options.lease,
|
|
8102
8195
|
workspaceDir: options.workspaceDir,
|
|
8103
8196
|
surface,
|
|
8104
|
-
onToolCall: (
|
|
8105
|
-
toolCalls.push(
|
|
8106
|
-
options.onToolCall?.(
|
|
8197
|
+
onToolCall: (call4) => {
|
|
8198
|
+
toolCalls.push(call4);
|
|
8199
|
+
options.onToolCall?.(call4);
|
|
8107
8200
|
}
|
|
8108
8201
|
});
|
|
8109
8202
|
const compaction = options.compaction === void 0 ? (0, import_ai4.keepRecentExchanges)({ whenInputTokensExceed: 12e4, keep: 3 }) : options.compaction;
|
|
@@ -8188,6 +8281,9 @@ async function handleCodeRuntimeInference(input) {
|
|
|
8188
8281
|
call: request3.call
|
|
8189
8282
|
});
|
|
8190
8283
|
state2.tokens += response2.receipt.inputTokens + response2.receipt.outputTokens;
|
|
8284
|
+
const { costUsd } = response2.receipt;
|
|
8285
|
+
if (costUsd === void 0) state2.costKnown = false;
|
|
8286
|
+
else state2.costUsd += costUsd;
|
|
8191
8287
|
await input.event({
|
|
8192
8288
|
type: "usage",
|
|
8193
8289
|
provider: response2.receipt.provider,
|
|
@@ -8197,7 +8293,9 @@ async function handleCodeRuntimeInference(input) {
|
|
|
8197
8293
|
durationMs: Date.now() - startedAt,
|
|
8198
8294
|
interactionId: command.commandId,
|
|
8199
8295
|
interactionTokens: state2.tokens,
|
|
8200
|
-
interactionMaxTokens: metadata2.maxTokensPerInteraction
|
|
8296
|
+
interactionMaxTokens: metadata2.maxTokensPerInteraction,
|
|
8297
|
+
...costUsd === void 0 ? {} : { costUsd },
|
|
8298
|
+
...state2.costKnown ? { interactionCostUsd: state2.costUsd } : {}
|
|
8201
8299
|
}).catch(() => void 0);
|
|
8202
8300
|
return {
|
|
8203
8301
|
protocolVersion: HARNESS_PROTOCOL_VERSION,
|
|
@@ -9041,10 +9139,16 @@ async function startGoalPursuit(input) {
|
|
|
9041
9139
|
attempt: async ({ prompt }) => {
|
|
9042
9140
|
const result = await input.attempt(prompt);
|
|
9043
9141
|
return {
|
|
9044
|
-
//
|
|
9045
|
-
//
|
|
9046
|
-
//
|
|
9047
|
-
|
|
9142
|
+
// What the attempt actually spent, so the runner's token_budget and
|
|
9143
|
+
// cost_budget checks can be reached. This used to be a hardcoded 0 with
|
|
9144
|
+
// no cost at all, which made maxTokens and maxUsd unreachable while
|
|
9145
|
+
// callers reasonably read them as hard ceilings.
|
|
9146
|
+
//
|
|
9147
|
+
// costUsd is omitted rather than zeroed when any call in the attempt
|
|
9148
|
+
// was unpriced: the runner only enforces a cost budget while the cost
|
|
9149
|
+
// is known, and a zero would make it enforce against a lie.
|
|
9150
|
+
tokens: result.tokens ?? 0,
|
|
9151
|
+
...result.costUsd === void 0 ? {} : { costUsd: result.costUsd },
|
|
9048
9152
|
...result.status === "failed" ? { error: result.error ?? "attempt failed" } : {}
|
|
9049
9153
|
};
|
|
9050
9154
|
},
|
|
@@ -9258,7 +9362,7 @@ var CodePiRuntimeEngine = class {
|
|
|
9258
9362
|
recipeAuthorization: this.options.recipeAuthorization
|
|
9259
9363
|
}, lease, metadata2.role));
|
|
9260
9364
|
const startedAt = Date.now();
|
|
9261
|
-
const interaction = { tokens: 0, noticeEmitted: false };
|
|
9365
|
+
const interaction = { tokens: 0, noticeEmitted: false, costUsd: 0, costKnown: true };
|
|
9262
9366
|
const inference = createCodeRuntimeInference({
|
|
9263
9367
|
command,
|
|
9264
9368
|
metadata: metadata2,
|
|
@@ -9296,7 +9400,11 @@ var CodePiRuntimeEngine = class {
|
|
|
9296
9400
|
await this.#diagnostic(command, active, detail);
|
|
9297
9401
|
await this.#failure(command, active, detail);
|
|
9298
9402
|
}
|
|
9299
|
-
return
|
|
9403
|
+
return {
|
|
9404
|
+
...result,
|
|
9405
|
+
tokens: interaction.tokens,
|
|
9406
|
+
...interaction.costKnown ? { costUsd: interaction.costUsd } : {}
|
|
9407
|
+
};
|
|
9300
9408
|
}
|
|
9301
9409
|
/** Report every brokered effect as it starts and finishes. */
|
|
9302
9410
|
#observed(command, active, broker) {
|
|
@@ -10405,6 +10513,8 @@ Usage:
|
|
|
10405
10513
|
odla-ai admin ai credential set <provider> [--context <name>] (--from-env <NAME>|--stdin)
|
|
10406
10514
|
odla-ai admin ai usage [--context <name>] [--app-id <id>] [--env <env>] [--run-id <id>] [--limit <1-500>] [--json]
|
|
10407
10515
|
odla-ai admin ai audit [--context <name>] [--limit <1-200>] [--json]
|
|
10516
|
+
odla-ai admin spend show <app:<id>:<incarnation>> [--context <name>] [--json]
|
|
10517
|
+
odla-ai admin spend reset <app:<id>:<incarnation>> [--context <name>] [--json]
|
|
10408
10518
|
odla-ai security github connect [--repo owner/name] [--env dev] [continue in Studio; human session required]
|
|
10409
10519
|
odla-ai security github disconnect --source <id> [--env dev] [continue in Studio; human session required]
|
|
10410
10520
|
odla-ai security plan [--env dev] [--json]
|
|
@@ -10497,6 +10607,9 @@ Commands:
|
|
|
10497
10607
|
human session connects one); "code grant request|list|approve|revoke" then
|
|
10498
10608
|
governs unattended access: an agent may request, only a human may approve.
|
|
10499
10609
|
admin Manage platform-funded AI routing/credentials/usage with narrow device grants.
|
|
10610
|
+
"admin spend" reads one app's daily inference spend and resumes it
|
|
10611
|
+
after a cap halts it. The cap is per UTC day; exhausting it LATCHES,
|
|
10612
|
+
and a new day does not clear the latch \u2014 resuming is deliberate.
|
|
10500
10613
|
security Connect GitHub sources and run commit-pinned hosted reviews, or scan a local snapshot.
|
|
10501
10614
|
pm Project management (via @odla-ai/pm): Products contain Projects;
|
|
10502
10615
|
projects contain goals, kanban tasks, decisions, and bugs. Use
|
|
@@ -10504,6 +10617,12 @@ Commands:
|
|
|
10504
10617
|
pass --app/--project explicitly. Same device-grant auth as "app".
|
|
10505
10618
|
Status changes and comments post to each item's @odla-ai/chat
|
|
10506
10619
|
discussion thread.
|
|
10620
|
+
NOTE: "--column ready" is OWNER-ONLY. Creating a task in Ready
|
|
10621
|
+
approves its complete execution contract, so it needs pm.plan,
|
|
10622
|
+
which no device enrollment or handshake approval can grant. An
|
|
10623
|
+
agent proposes in Backlog (the default); a human owner or a
|
|
10624
|
+
pm.plan agent promotes. This is deliberate, not a permission
|
|
10625
|
+
gap \u2014 it was reported as one.
|
|
10507
10626
|
bug Intent-first alias for PM bugs. "bug report" writes to
|
|
10508
10627
|
odla PM; odla product defects do not belong in GitHub Issues.
|
|
10509
10628
|
discuss Group discussions (via @odla-ai/chat) for the apps you co-own:
|
|
@@ -12884,9 +13003,15 @@ async function provisionEnvCredentials(opts) {
|
|
|
12884
13003
|
if (o11yToken) {
|
|
12885
13004
|
opts.stdout.log(`${opts.env}: reusing local o11y ingest token`);
|
|
12886
13005
|
} else {
|
|
12887
|
-
o11yToken = await issueO11yToken(opts);
|
|
12888
|
-
|
|
12889
|
-
|
|
13006
|
+
o11yToken = await issueO11yToken(opts) ?? void 0;
|
|
13007
|
+
if (o11yToken) {
|
|
13008
|
+
credentials = save(opts, credentials, tenantId, { ...dbKey ? { dbKey } : {}, o11yToken });
|
|
13009
|
+
opts.stdout.log(`${opts.env}: ${opts.rotateO11y ? "rotated" : "issued"} o11y ingest token`);
|
|
13010
|
+
} else {
|
|
13011
|
+
opts.stdout.log(
|
|
13012
|
+
`${opts.env}: o11y token already exists and its shown-once value is not on this machine \u2014 leaving it alone (a co-owner provisioned this env). Nothing was rotated, and this env's running Worker keeps its token. To replace it deliberately \u2014 which invalidates the token on whatever is deployed \u2014 run "odla-ai provision --rotate-o11y-token --push-secrets".`
|
|
13013
|
+
);
|
|
13014
|
+
}
|
|
12890
13015
|
}
|
|
12891
13016
|
}
|
|
12892
13017
|
return save(opts, credentials, tenantId, {
|
|
@@ -12940,11 +13065,7 @@ async function issueO11yToken(opts) {
|
|
|
12940
13065
|
`${opts.cfg.platformUrl}/o11y/${encodeURIComponent(opts.cfg.app.id)}/token${suffix}?env=${encodeURIComponent(opts.env)}`,
|
|
12941
13066
|
{ method: "POST", headers: { authorization: `Bearer ${opts.developerToken}` } }
|
|
12942
13067
|
);
|
|
12943
|
-
if (res.status === 409 && !opts.rotateO11y)
|
|
12944
|
-
throw new Error(
|
|
12945
|
-
`o11y token already exists for env "${opts.env}", but its shown-once value is not in the local credentials file; run "odla-ai provision --push-secrets" to install a separate runtime credential without rotating siblings`
|
|
12946
|
-
);
|
|
12947
|
-
}
|
|
13068
|
+
if (res.status === 409 && !opts.rotateO11y) return null;
|
|
12948
13069
|
if (!res.ok) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) failed: ${res.status} ${await safeText7(res)}`);
|
|
12949
13070
|
const body = await res.json();
|
|
12950
13071
|
if (!body.token) throw new Error(`o11y token ${opts.rotateO11y ? "rotation" : "issue"} (${opts.env}) returned no token`);
|
|
@@ -13688,7 +13809,7 @@ function describeUnmet(slug, unmet) {
|
|
|
13688
13809
|
|
|
13689
13810
|
// src/runbook-actions.ts
|
|
13690
13811
|
var PLATFORM_SCOPE = "$platform";
|
|
13691
|
-
async function
|
|
13812
|
+
async function call2(ctx, method, path, body) {
|
|
13692
13813
|
const res = await ctx.doFetch(`${ctx.platformUrl.replace(/\/$/, "")}/registry/pm${path}`, {
|
|
13693
13814
|
method,
|
|
13694
13815
|
headers: {
|
|
@@ -13710,7 +13831,7 @@ async function call(ctx, method, path, body) {
|
|
|
13710
13831
|
throw new Error(message2);
|
|
13711
13832
|
}
|
|
13712
13833
|
async function bySlug(ctx, slug) {
|
|
13713
|
-
const page2 = await
|
|
13834
|
+
const page2 = await call2(
|
|
13714
13835
|
ctx,
|
|
13715
13836
|
"GET",
|
|
13716
13837
|
`/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(slug)}&limit=1`
|
|
@@ -13719,7 +13840,7 @@ async function bySlug(ctx, slug) {
|
|
|
13719
13840
|
if (filtered) return filtered;
|
|
13720
13841
|
const limit = 100;
|
|
13721
13842
|
for (let offset = 0; ; offset += limit) {
|
|
13722
|
-
const fallback = await
|
|
13843
|
+
const fallback = await call2(
|
|
13723
13844
|
ctx,
|
|
13724
13845
|
"GET",
|
|
13725
13846
|
`/runbook?app=${encodeURIComponent(ctx.appId)}&limit=${limit}&offset=${offset}`
|
|
@@ -13740,7 +13861,7 @@ async function runbookList(ctx, all, query) {
|
|
|
13740
13861
|
const params = new URLSearchParams();
|
|
13741
13862
|
if (!all) params.set("app", ctx.appId);
|
|
13742
13863
|
if (query) params.set("q", query);
|
|
13743
|
-
const page2 = await
|
|
13864
|
+
const page2 = await call2(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
|
|
13744
13865
|
if (ctx.json) return ctx.out.log(JSON.stringify(page2, null, 2));
|
|
13745
13866
|
if (!page2.records.length) return ctx.out.log("(no runbooks)");
|
|
13746
13867
|
ctx.out.log(["SLUG", "STATUS", "V", "SCOPE", "UPDATED", "TITLE"].join(" "));
|
|
@@ -13757,7 +13878,7 @@ async function runbookGet(ctx, slug) {
|
|
|
13757
13878
|
ctx.out.log(runbook.body);
|
|
13758
13879
|
}
|
|
13759
13880
|
async function runbookNew(ctx, slug, title, body, summary, requires) {
|
|
13760
|
-
const created = await
|
|
13881
|
+
const created = await call2(ctx, "POST", "/runbook", {
|
|
13761
13882
|
appId: ctx.appId,
|
|
13762
13883
|
input: { slug, title, body, ...summary ? { summary } : {}, ...requires ? { requires } : {} }
|
|
13763
13884
|
});
|
|
@@ -13765,7 +13886,7 @@ async function runbookNew(ctx, slug, title, body, summary, requires) {
|
|
|
13765
13886
|
}
|
|
13766
13887
|
async function runbookEdit(ctx, slug, body, note, requires, expectedVersion) {
|
|
13767
13888
|
const runbook = await bySlug(ctx, slug);
|
|
13768
|
-
const result = await
|
|
13889
|
+
const result = await call2(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
|
|
13769
13890
|
// An empty --requires clears the declaration; omitting the flag leaves
|
|
13770
13891
|
// whatever is there, so an ordinary body edit never drops it.
|
|
13771
13892
|
patch: {
|
|
@@ -13780,7 +13901,7 @@ async function runbookEdit(ctx, slug, body, note, requires, expectedVersion) {
|
|
|
13780
13901
|
}
|
|
13781
13902
|
async function runbookStatus(ctx, slug, status) {
|
|
13782
13903
|
const runbook = await bySlug(ctx, slug);
|
|
13783
|
-
const result = await
|
|
13904
|
+
const result = await call2(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
|
|
13784
13905
|
patch: { status }
|
|
13785
13906
|
});
|
|
13786
13907
|
ctx.out.log(ctx.json ? JSON.stringify(result, null, 2) : `${slug} \u2192 ${status}`);
|
|
@@ -13789,7 +13910,7 @@ async function runbookVisibility(ctx, slug, visibility) {
|
|
|
13789
13910
|
if (visibility !== "operator" && visibility !== "admin")
|
|
13790
13911
|
throw new Error(`visibility must be "operator" or "admin", got "${visibility}"`);
|
|
13791
13912
|
const runbook = await bySlug(ctx, slug);
|
|
13792
|
-
const result = await
|
|
13913
|
+
const result = await call2(ctx, "PATCH", `/runbook/${encodeURIComponent(runbook.id)}`, {
|
|
13793
13914
|
patch: { visibility }
|
|
13794
13915
|
});
|
|
13795
13916
|
if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
|
|
@@ -13799,7 +13920,7 @@ async function runbookVisibility(ctx, slug, visibility) {
|
|
|
13799
13920
|
}
|
|
13800
13921
|
async function runbookHistory(ctx, slug) {
|
|
13801
13922
|
const runbook = await bySlug(ctx, slug);
|
|
13802
|
-
const page2 = await
|
|
13923
|
+
const page2 = await call2(ctx, "GET", `/runbook/${encodeURIComponent(runbook.id)}/revisions`);
|
|
13803
13924
|
if (ctx.json) return ctx.out.log(JSON.stringify(page2, null, 2));
|
|
13804
13925
|
ctx.out.log(`${slug} is at v${runbook.version}`);
|
|
13805
13926
|
if (!page2.records.length) return ctx.out.log("(no earlier versions)");
|
|
@@ -13809,7 +13930,7 @@ async function runbookHistory(ctx, slug) {
|
|
|
13809
13930
|
}
|
|
13810
13931
|
async function runbookRevert(ctx, slug, version) {
|
|
13811
13932
|
const runbook = await bySlug(ctx, slug);
|
|
13812
|
-
const result = await
|
|
13933
|
+
const result = await call2(
|
|
13813
13934
|
ctx,
|
|
13814
13935
|
"POST",
|
|
13815
13936
|
`/runbook/${encodeURIComponent(runbook.id)}/revert`,
|
|
@@ -13820,7 +13941,7 @@ async function runbookRevert(ctx, slug, version) {
|
|
|
13820
13941
|
}
|
|
13821
13942
|
async function runbookRemove(ctx, slug) {
|
|
13822
13943
|
const runbook = await bySlug(ctx, slug);
|
|
13823
|
-
await
|
|
13944
|
+
await call2(ctx, "DELETE", `/runbook/${encodeURIComponent(runbook.id)}`);
|
|
13824
13945
|
ctx.out.log(`removed ${slug}`);
|
|
13825
13946
|
}
|
|
13826
13947
|
|
|
@@ -13879,14 +14000,9 @@ ${counts.created} created, ${counts.updated} updated, ${counts.unchanged} unchan
|
|
|
13879
14000
|
);
|
|
13880
14001
|
}
|
|
13881
14002
|
async function upsert(ctx, r, visibility) {
|
|
13882
|
-
const
|
|
13883
|
-
ctx,
|
|
13884
|
-
"GET",
|
|
13885
|
-
`/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(r.slug)}&limit=1`
|
|
13886
|
-
);
|
|
13887
|
-
const found = page2.records[0];
|
|
14003
|
+
const found = await bySlug(ctx, r.slug).catch(() => null);
|
|
13888
14004
|
if (!found) {
|
|
13889
|
-
await
|
|
14005
|
+
await call2(ctx, "POST", "/runbook", {
|
|
13890
14006
|
appId: ctx.appId,
|
|
13891
14007
|
input: {
|
|
13892
14008
|
slug: r.slug,
|
|
@@ -13905,7 +14021,7 @@ async function upsert(ctx, r, visibility) {
|
|
|
13905
14021
|
ctx.out.log(` = ${r.slug} (already current at v${found.version})`);
|
|
13906
14022
|
return "unchanged";
|
|
13907
14023
|
}
|
|
13908
|
-
const result = await
|
|
14024
|
+
const result = await call2(
|
|
13909
14025
|
ctx,
|
|
13910
14026
|
"PATCH",
|
|
13911
14027
|
`/runbook/${encodeURIComponent(found.id)}`,
|
|
@@ -14133,7 +14249,7 @@ async function assessImpact(ctx, surfaces, all, limit) {
|
|
|
14133
14249
|
for (const surface of surfaces) {
|
|
14134
14250
|
const params = new URLSearchParams({ q: surface.query, limit: String(limit) });
|
|
14135
14251
|
if (!all) params.set("app", ctx.appId);
|
|
14136
|
-
const result = await
|
|
14252
|
+
const result = await call2(ctx, "GET", `/runbook/search?${params}`);
|
|
14137
14253
|
const runbooks = result.outcome === "ranked" ? foldHits(result.hits) : result.candidates.map((c) => ({ ...c, version: 0, sections: [] }));
|
|
14138
14254
|
out.push({ surface, runbooks });
|
|
14139
14255
|
}
|
|
@@ -14216,7 +14332,7 @@ function lintRunbook(runbook, installed) {
|
|
|
14216
14332
|
async function runbookLint(ctx, all) {
|
|
14217
14333
|
const params = new URLSearchParams();
|
|
14218
14334
|
if (!all) params.set("app", ctx.appId);
|
|
14219
|
-
const page2 = await
|
|
14335
|
+
const page2 = await call2(ctx, "GET", `/runbook${params.size ? `?${params}` : ""}`);
|
|
14220
14336
|
const installed = { "@odla-ai/cli": cliVersion() };
|
|
14221
14337
|
const findings = page2.records.flatMap((runbook) => lintRunbook(runbook, installed));
|
|
14222
14338
|
if (ctx.json) return ctx.out.log(JSON.stringify({ checked: page2.records.length, findings }, null, 2));
|
|
@@ -14241,7 +14357,7 @@ async function runbookSearch(ctx, query, all, limit) {
|
|
|
14241
14357
|
const params = new URLSearchParams({ q: query });
|
|
14242
14358
|
if (!all) params.set("app", ctx.appId);
|
|
14243
14359
|
if (limit) params.set("limit", String(limit));
|
|
14244
|
-
const result = await
|
|
14360
|
+
const result = await call2(ctx, "GET", `/runbook/search?${params}`);
|
|
14245
14361
|
if (ctx.json) return ctx.out.log(JSON.stringify(result, null, 2));
|
|
14246
14362
|
if (result.outcome === "empty-corpus") return ctx.out.log("no runbooks are available to search");
|
|
14247
14363
|
if (result.outcome === "no-match") return ctx.out.log(`no runbook mentions "${query}"`);
|
|
@@ -14260,7 +14376,7 @@ async function runbookSearch(ctx, query, all, limit) {
|
|
|
14260
14376
|
}
|
|
14261
14377
|
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.";
|
|
14262
14378
|
async function runbookAsk(ctx, question, all) {
|
|
14263
|
-
const result = await
|
|
14379
|
+
const result = await call2(ctx, "POST", "/runbook/ask", {
|
|
14264
14380
|
question,
|
|
14265
14381
|
...all ? {} : { app: ctx.appId }
|
|
14266
14382
|
});
|
|
@@ -14289,14 +14405,8 @@ async function runbookAsk(ctx, question, all) {
|
|
|
14289
14405
|
ctx.out.log(JSDOC_POINTER);
|
|
14290
14406
|
}
|
|
14291
14407
|
async function runbookComment(ctx, slug, body) {
|
|
14292
|
-
const
|
|
14293
|
-
|
|
14294
|
-
"GET",
|
|
14295
|
-
`/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(slug)}&limit=1`
|
|
14296
|
-
);
|
|
14297
|
-
const found = page2.records[0];
|
|
14298
|
-
if (!found) throw new Error(`no runbook "${slug}" in ${ctx.appId}`);
|
|
14299
|
-
await call(ctx, "POST", `/runbook/${encodeURIComponent(found.id)}/comments`, { body });
|
|
14408
|
+
const found = await bySlug(ctx, slug);
|
|
14409
|
+
await call2(ctx, "POST", `/runbook/${encodeURIComponent(found.id)}/comments`, { body });
|
|
14300
14410
|
ctx.out.log(`commented on ${slug} (v${found.version})`);
|
|
14301
14411
|
}
|
|
14302
14412
|
|
|
@@ -14346,13 +14456,7 @@ var defaultRunOrInjected = (deps) => deps.run ?? defaultRun;
|
|
|
14346
14456
|
|
|
14347
14457
|
// src/runbook-edit-flow.ts
|
|
14348
14458
|
async function editRunbook(ctx, slug, deps = {}) {
|
|
14349
|
-
const
|
|
14350
|
-
ctx,
|
|
14351
|
-
"GET",
|
|
14352
|
-
`/runbook?app=${encodeURIComponent(ctx.appId)}&slug=${encodeURIComponent(slug)}&limit=1`
|
|
14353
|
-
);
|
|
14354
|
-
const found = page2.records[0];
|
|
14355
|
-
if (!found) throw new Error(`no runbook "${slug}" in ${ctx.appId}`);
|
|
14459
|
+
const found = await bySlug(ctx, slug);
|
|
14356
14460
|
ctx.out.log(`opening ${slug} v${found.version} in your editor\u2026`);
|
|
14357
14461
|
const body = await editText(found.body, slug, deps);
|
|
14358
14462
|
return body === null ? null : { body, expectedVersion: found.version };
|