@lucid-agents/taskmarket 1.17.0 → 1.17.2
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/agent-compatibility.json +1 -1
- package/dist/budget-view-XKZC3GY5.js +36 -0
- package/dist/channels-JF4Q5KG3.js +29 -0
- package/dist/chunk-52Q2DCZN.js +1099 -0
- package/dist/chunk-73RXOYME.js +185 -0
- package/dist/{chunk-3XPF2O6L.js → chunk-FRV3ZHUR.js} +5381 -6300
- package/dist/chunk-GJQUALXN.js +1062 -0
- package/dist/chunk-LDCFKFF6.js +154 -0
- package/dist/{compatibility-F3RC2GX7.js → chunk-OKZ2NRYZ.js} +5 -5
- package/dist/chunk-XXD7TWLP.js +181 -0
- package/dist/{chunk-O27J7ZPH.js → chunk-YM4S6IE4.js} +2 -1
- package/dist/compatibility-KUYGZSUC.js +19 -0
- package/dist/doctor-7TWLIJWW.js +216 -0
- package/dist/{errors-F3EGNA4F.js → errors-2L357HGA.js} +1 -1
- package/dist/index.js +317 -162
- package/dist/{launch-EZJGJJ5Y.js → launch-2U3KFTFD.js} +1414 -700
- package/dist/pi-extensions/remote-tools.cjs +103 -0
- package/dist/pi-extensions/xgate.cjs +310 -5
- package/dist/reconcile-XZR2UYSW.js +83 -0
- package/dist/status-5IMULBQQ.js +18 -0
- package/package.json +3 -3
|
@@ -20503,6 +20503,14 @@ var OutputLimitError = class extends Error {
|
|
|
20503
20503
|
this.name = "OutputLimitError";
|
|
20504
20504
|
}
|
|
20505
20505
|
};
|
|
20506
|
+
var ManifestLimitError = class extends Error {
|
|
20507
|
+
limit;
|
|
20508
|
+
constructor(limit) {
|
|
20509
|
+
super(`manifest_limit:${limit}`);
|
|
20510
|
+
this.limit = limit;
|
|
20511
|
+
this.name = "ManifestLimitError";
|
|
20512
|
+
}
|
|
20513
|
+
};
|
|
20506
20514
|
|
|
20507
20515
|
// ../../packages/remote-tools/dist/channel.js
|
|
20508
20516
|
var MAX_MESSAGE_BYTES = 64 * 1024;
|
|
@@ -20731,6 +20739,7 @@ var MAX_CONTEXT_FILE_BYTES = 64 * 1024;
|
|
|
20731
20739
|
var MAX_RESULT_LIMIT = 1e4;
|
|
20732
20740
|
var MAX_GREP_CONTEXT = 50;
|
|
20733
20741
|
var HEARTBEAT_INTERVAL_MS = 6e4;
|
|
20742
|
+
var SYNC_MAX_TOTAL_BYTES = 256 * 1024 * 1024;
|
|
20734
20743
|
|
|
20735
20744
|
// ../../packages/remote-tools/dist/prompt.js
|
|
20736
20745
|
var REMOTE_CONTEXT_FILE_NAMES = ["AGENTS.md", "CLAUDE.md"];
|
|
@@ -21337,6 +21346,7 @@ function shellQuote(value) {
|
|
|
21337
21346
|
}
|
|
21338
21347
|
var EXIT_NOT_FOUND = 64;
|
|
21339
21348
|
var EXIT_NOT_DIR = 65;
|
|
21349
|
+
var EXIT_IS_DIR = 66;
|
|
21340
21350
|
var KILL_GRACE_SECONDS = 2;
|
|
21341
21351
|
function unitName(sessionTag, n) {
|
|
21342
21352
|
if (!/^[a-z0-9]{1,16}$/.test(sessionTag))
|
|
@@ -21401,6 +21411,49 @@ function globCommand(dir, pattern, ignore, limit) {
|
|
|
21401
21411
|
args.push("--glob", shellQuote(`!${glob}`));
|
|
21402
21412
|
return `cd -- ${shellQuote(dir)} 2>/dev/null || { [ -e ${shellQuote(dir)} ] && exit ${EXIT_NOT_DIR}; exit ${EXIT_NOT_FOUND}; }; ${args.join(" ")} | head -z -n ${Math.max(1, Math.floor(limit))}; rc=\${PIPESTATUS[0]}; [ "$rc" -eq 141 ] && exit 0; exit "$rc"`;
|
|
21403
21413
|
}
|
|
21414
|
+
function manifestCommand(dir, prune) {
|
|
21415
|
+
const pruned = prune.length ? `\\( ${prune.map((name) => `-name ${shellQuote(name)}`).join(" -o ")} \\) -prune -o ` : "";
|
|
21416
|
+
const find = `find . -mindepth 1 ${pruned}`;
|
|
21417
|
+
return [
|
|
21418
|
+
`cd -- ${shellQuote(dir)} 2>/dev/null || { [ -e ${shellQuote(dir)} ] && exit ${EXIT_NOT_DIR}; exit ${EXIT_NOT_FOUND}; }`,
|
|
21419
|
+
`${find}\\( -type f -o -type l \\) -printf '%y\\0%s\\0%l\\0%P\\0' || exit 1`,
|
|
21420
|
+
`printf '=\\0'`,
|
|
21421
|
+
`${find}-type f -printf '%P\\0' | xargs -0 -r sha256sum -z -- 2>/dev/null`,
|
|
21422
|
+
"exit 0"
|
|
21423
|
+
].join("\n");
|
|
21424
|
+
}
|
|
21425
|
+
function parseManifestOutput(stdout) {
|
|
21426
|
+
const fields = stdout.toString("utf8").split("\0");
|
|
21427
|
+
let separator = 0;
|
|
21428
|
+
while (separator < fields.length && fields[separator] !== "=")
|
|
21429
|
+
separator += 4;
|
|
21430
|
+
if (separator >= fields.length)
|
|
21431
|
+
throw new Error("Malformed manifest output");
|
|
21432
|
+
const hashes = /* @__PURE__ */ new Map();
|
|
21433
|
+
for (const line of fields.slice(separator + 1)) {
|
|
21434
|
+
const match = /^([0-9a-f]{64}) [ *](.*)$/s.exec(line);
|
|
21435
|
+
if (match)
|
|
21436
|
+
hashes.set(match[2], match[1]);
|
|
21437
|
+
}
|
|
21438
|
+
const entries = [];
|
|
21439
|
+
for (let i = 0; i < separator; i += 4) {
|
|
21440
|
+
const [type, size, target, rel] = fields.slice(i, i + 4);
|
|
21441
|
+
if (type === "l")
|
|
21442
|
+
entries.push({ path: rel, type: "symlink", target });
|
|
21443
|
+
else if (type === "f") {
|
|
21444
|
+
entries.push({ path: rel, type: "file", size: Number(size), sha256: hashes.get(rel) ?? "" });
|
|
21445
|
+
}
|
|
21446
|
+
}
|
|
21447
|
+
return entries.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
21448
|
+
}
|
|
21449
|
+
function symlinkCommand(linkPath, target) {
|
|
21450
|
+
const p = shellQuote(linkPath);
|
|
21451
|
+
return `if [ -d ${p} ] && [ ! -L ${p} ]; then exit ${EXIT_IS_DIR}; fi; mkdir -p -- "$(dirname -- ${p})" && ln -sfn -- ${shellQuote(target)} ${p}`;
|
|
21452
|
+
}
|
|
21453
|
+
function removeCommand(filePath) {
|
|
21454
|
+
const p = shellQuote(filePath);
|
|
21455
|
+
return `if [ -d ${p} ] && [ ! -L ${p} ]; then exit ${EXIT_IS_DIR}; fi; rm -f -- ${p}`;
|
|
21456
|
+
}
|
|
21404
21457
|
function parseGlobOutput(stdout) {
|
|
21405
21458
|
return stdout.toString("utf8").split("\0").filter((entry) => entry.length > 0);
|
|
21406
21459
|
}
|
|
@@ -22291,6 +22344,56 @@ var SshSandboxBackend = class {
|
|
|
22291
22344
|
}
|
|
22292
22345
|
return { isDirectory: kind === "D" };
|
|
22293
22346
|
}
|
|
22347
|
+
async manifest(dir, options) {
|
|
22348
|
+
const stop = new AbortController();
|
|
22349
|
+
const onAbort = () => stop.abort();
|
|
22350
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
22351
|
+
const out = [];
|
|
22352
|
+
let entries = 0;
|
|
22353
|
+
let overLimit = false;
|
|
22354
|
+
try {
|
|
22355
|
+
const { exitCode, stderr } = await this.collect(manifestCommand(dir, options.prune), stop.signal, (chunk) => {
|
|
22356
|
+
out.push(chunk);
|
|
22357
|
+
for (const byte of chunk)
|
|
22358
|
+
if (byte === 0)
|
|
22359
|
+
entries += 1;
|
|
22360
|
+
if (entries > (options.limit + 1) * 5 && !overLimit) {
|
|
22361
|
+
overLimit = true;
|
|
22362
|
+
stop.abort();
|
|
22363
|
+
}
|
|
22364
|
+
});
|
|
22365
|
+
if (exitCode === EXIT_NOT_FOUND)
|
|
22366
|
+
throw new SandboxFileError("ENOENT", "scandir", dir);
|
|
22367
|
+
if (exitCode === EXIT_NOT_DIR)
|
|
22368
|
+
throw new SandboxFileError("ENOTDIR", "scandir", dir);
|
|
22369
|
+
if (exitCode !== 0)
|
|
22370
|
+
throw new Error(`find failed in the sandbox (exit ${exitCode}): ${stderr}`);
|
|
22371
|
+
} catch (error) {
|
|
22372
|
+
if (overLimit)
|
|
22373
|
+
throw new ManifestLimitError(options.limit);
|
|
22374
|
+
throw error;
|
|
22375
|
+
} finally {
|
|
22376
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
22377
|
+
}
|
|
22378
|
+
const parsed = parseManifestOutput(Buffer.concat(out));
|
|
22379
|
+
if (parsed.length > options.limit)
|
|
22380
|
+
throw new ManifestLimitError(options.limit);
|
|
22381
|
+
return parsed;
|
|
22382
|
+
}
|
|
22383
|
+
async symlink(path6, target, options = {}) {
|
|
22384
|
+
await this.fileCommand(symlinkCommand(path6, target), "symlink", path6, options.signal);
|
|
22385
|
+
}
|
|
22386
|
+
async remove(path6, options = {}) {
|
|
22387
|
+
await this.fileCommand(removeCommand(path6), "unlink", path6, options.signal);
|
|
22388
|
+
}
|
|
22389
|
+
async fileCommand(command, syscall, path6, signal) {
|
|
22390
|
+
const { exitCode, stderr } = await this.collect(command, signal, () => void 0);
|
|
22391
|
+
if (exitCode === EXIT_IS_DIR)
|
|
22392
|
+
throw new SandboxFileError("EISDIR", syscall, path6);
|
|
22393
|
+
if (exitCode !== 0) {
|
|
22394
|
+
throw new SandboxFileError("EIO", syscall, path6, stderr || `exit ${exitCode}`);
|
|
22395
|
+
}
|
|
22396
|
+
}
|
|
22294
22397
|
async exec(command, cwd, options) {
|
|
22295
22398
|
const { conn } = await this.ensure(options.signal);
|
|
22296
22399
|
const unit = this.nextUnit();
|
|
@@ -2006,11 +2006,293 @@ init_cjs_shims();
|
|
|
2006
2006
|
var import_node_path4 = __toESM(require("path"), 1);
|
|
2007
2007
|
var import_pi_coding_agent = require("@earendil-works/pi-coding-agent");
|
|
2008
2008
|
|
|
2009
|
+
// src/lib/agent/budget-ui.ts
|
|
2010
|
+
init_cjs_shims();
|
|
2011
|
+
|
|
2012
|
+
// src/lib/agent/budget-view.ts
|
|
2013
|
+
init_cjs_shims();
|
|
2014
|
+
var USDC_DECIMALS = 6;
|
|
2015
|
+
function formatUsdc(atomic2) {
|
|
2016
|
+
let value;
|
|
2017
|
+
try {
|
|
2018
|
+
value = typeof atomic2 === "bigint" ? atomic2 : BigInt(atomic2);
|
|
2019
|
+
} catch {
|
|
2020
|
+
return "$?";
|
|
2021
|
+
}
|
|
2022
|
+
const negative = value < 0n;
|
|
2023
|
+
const abs = negative ? -value : value;
|
|
2024
|
+
const scale = 10n ** BigInt(USDC_DECIMALS);
|
|
2025
|
+
const fraction = (abs % scale).toString().padStart(USDC_DECIMALS, "0").replace(/0+$/, "");
|
|
2026
|
+
return `${negative ? "-" : ""}$${abs / scale}.${fraction.padEnd(2, "0")}`;
|
|
2027
|
+
}
|
|
2028
|
+
function formatCredits(value) {
|
|
2029
|
+
const number = Number(value);
|
|
2030
|
+
if (!Number.isFinite(number)) return value;
|
|
2031
|
+
return number >= 100 ? number.toFixed(0) : number.toFixed(1);
|
|
2032
|
+
}
|
|
2033
|
+
function remainingBudget(view) {
|
|
2034
|
+
const channels = view.channels.filter((channel) => channel.state === "open" && !channel.retired).reduce(
|
|
2035
|
+
(sum, channel) => sum + BigInt(channel.amounts.refundable) + BigInt(channel.amounts.lifetimeRoom),
|
|
2036
|
+
0n
|
|
2037
|
+
);
|
|
2038
|
+
if (!view.budget) return channels;
|
|
2039
|
+
const left = BigInt(view.budget.cap) - BigInt(view.budget.used);
|
|
2040
|
+
const budget = left > 0n ? left : 0n;
|
|
2041
|
+
return channels < budget ? channels : budget;
|
|
2042
|
+
}
|
|
2043
|
+
function blockingPurchases(view) {
|
|
2044
|
+
const ids = new Set(view.unresolved);
|
|
2045
|
+
for (const channel of view.channels) {
|
|
2046
|
+
if (channel.pendingOperation?.kind === "payment") ids.add(channel.pendingOperation.id);
|
|
2047
|
+
}
|
|
2048
|
+
return [...ids];
|
|
2049
|
+
}
|
|
2050
|
+
function statusBarText(view) {
|
|
2051
|
+
const parts = [];
|
|
2052
|
+
parts.push(view.lastTurn ? `turn ${formatUsdc(view.lastTurn.chargedAtomic)}` : "turn -");
|
|
2053
|
+
const active = view.channels.filter((channel) => !channel.retired);
|
|
2054
|
+
if (active.length > 0) {
|
|
2055
|
+
const open = active.some((channel) => channel.state === "open");
|
|
2056
|
+
parts.push(
|
|
2057
|
+
open ? `budget ${formatUsdc(remainingBudget(view))} left` : `channel ${active[0].state.replace("_", " ")}`
|
|
2058
|
+
);
|
|
2059
|
+
}
|
|
2060
|
+
const compute = view.compute;
|
|
2061
|
+
if (compute?.configured && compute.availableCredits !== null) {
|
|
2062
|
+
const burn = compute.burnCreditsPerHour !== null ? ` at ${formatCredits(compute.burnCreditsPerHour)}/h` : "";
|
|
2063
|
+
parts.push(
|
|
2064
|
+
`compute ${formatCredits(compute.availableCredits)} cr${burn}${compute.low ? " LOW: /credits" : ""}`
|
|
2065
|
+
);
|
|
2066
|
+
} else if (compute && !compute.configured) {
|
|
2067
|
+
parts.push("compute: not set up");
|
|
2068
|
+
}
|
|
2069
|
+
const blocking = blockingPurchases(view);
|
|
2070
|
+
if (blocking.length > 0) {
|
|
2071
|
+
const id = blocking[0].length > 12 ? `${blocking[0].slice(0, 12)}...` : blocking[0];
|
|
2072
|
+
parts.push(`purchase ${id} unresolved: new turns blocked, see /budget`);
|
|
2073
|
+
}
|
|
2074
|
+
return parts.join(" | ");
|
|
2075
|
+
}
|
|
2076
|
+
function channelLines(channel) {
|
|
2077
|
+
const a = channel.amounts;
|
|
2078
|
+
return [
|
|
2079
|
+
`Channel ${channel.rail}${channel.retired ? " (retired)" : ""} ${channel.channelId} (${channel.network}, ${channel.route}): ${channel.state.replace("_", " ")}`,
|
|
2080
|
+
` Locked ${formatUsdc(a.locked)} (deposited, not spent); charged ${formatUsdc(a.charged)}; pending vouchers ${formatUsdc(a.pending)}; refunded ${formatUsdc(a.refunded)}`,
|
|
2081
|
+
` Refundable ${formatUsdc(a.refundable)}: what further turns can use and a close returns`,
|
|
2082
|
+
` Budget ${formatUsdc(a.budget)}; ${formatUsdc(a.lifetimeRoom)} may still be deposited; next deposit ${formatUsdc(a.depositTarget)}${Number(a.depositing) > 0 ? `; ${formatUsdc(a.depositing)} being deposited` : ""}`,
|
|
2083
|
+
...channel.pendingOperation ? [
|
|
2084
|
+
channel.pendingOperation.kind === "payment" ? ` Purchase ${channel.pendingOperation.id} is unresolved and blocks new turns: taskmarket agent reconcile ${channel.pendingOperation.id}` : ` A refund or withdrawal (${channel.pendingOperation.id}) is unresolved: taskmarket agent close checks it again`
|
|
2085
|
+
] : [],
|
|
2086
|
+
...channel.blockingDivergences.length > 0 ? [
|
|
2087
|
+
` Blocked: the journal, the router and the chain disagree (${channel.blockingDivergences.join(", ")})`
|
|
2088
|
+
] : []
|
|
2089
|
+
];
|
|
2090
|
+
}
|
|
2091
|
+
function budgetLines(view) {
|
|
2092
|
+
const lines2 = [
|
|
2093
|
+
`This session: ${view.session.turns} paid turn${view.session.turns === 1 ? "" : "s"}, ${formatUsdc(view.session.chargedAtomic)}${view.lastTurn ? `; last turn ${formatUsdc(view.lastTurn.chargedAtomic)}` : ""}`
|
|
2094
|
+
];
|
|
2095
|
+
for (const channel of view.channels) lines2.push(...channelLines(channel));
|
|
2096
|
+
for (const allocation of view.allocations) {
|
|
2097
|
+
lines2.push(
|
|
2098
|
+
`Allocation ${allocation.allocationId} (${allocation.exposure})${allocation.closed ? ", closed" : ""}: used ${formatUsdc(allocation.used.total)} of ${formatUsdc(allocation.caps.amount)}${allocation.caps.perDay ? `; today ${formatUsdc(allocation.used.today)} of ${formatUsdc(allocation.caps.perDay)}` : ""}`
|
|
2099
|
+
);
|
|
2100
|
+
}
|
|
2101
|
+
for (const id of blockingPurchases(view)) {
|
|
2102
|
+
lines2.push(uncertainPurchaseMessage(id));
|
|
2103
|
+
}
|
|
2104
|
+
const compute = view.compute;
|
|
2105
|
+
if (compute) lines2.push(...computeLines(compute));
|
|
2106
|
+
return lines2;
|
|
2107
|
+
}
|
|
2108
|
+
function computeLines(compute) {
|
|
2109
|
+
if (!compute.configured) {
|
|
2110
|
+
return ["Compute credit: not set up; run taskmarket agent credits setup"];
|
|
2111
|
+
}
|
|
2112
|
+
if (compute.error !== void 0 || compute.availableCredits === null) {
|
|
2113
|
+
return [`Compute credit: unavailable (${compute.error ?? "not read"})`];
|
|
2114
|
+
}
|
|
2115
|
+
return [
|
|
2116
|
+
`Compute credit: ${formatCredits(compute.availableCredits)} available${compute.burnCreditsPerHour !== null ? `, burning ${formatCredits(compute.burnCreditsPerHour)} an hour while the sandbox runs` : ""}${compute.low ? ". Low: buy more with /credits" : ""}`,
|
|
2117
|
+
...compute.pendingPurchases > 0 ? [
|
|
2118
|
+
` ${compute.pendingPurchases} purchase(s) not settled yet: taskmarket agent credits reconcile`
|
|
2119
|
+
] : [],
|
|
2120
|
+
...compute.autoTopUp ? [
|
|
2121
|
+
` Auto-top-up: $${compute.autoTopUp.amountUsd} below ${compute.autoTopUp.thresholdCredits} credits, at most ${compute.autoTopUp.maxChunksPerDay} a day`
|
|
2122
|
+
] : []
|
|
2123
|
+
];
|
|
2124
|
+
}
|
|
2125
|
+
var LOST_RESPONSE_TEXT = "It was charged, and the response cannot be recovered or regenerated: xgate delivers it live only and keeps no copy. Asking again is a new paid turn.";
|
|
2126
|
+
function uncertainPurchaseMessage(invocationId2) {
|
|
2127
|
+
return `The outcome of purchase ${invocationId2} is unknown. It is never sent again, and new turns on its channel are blocked until it is reconciled: taskmarket agent reconcile ${invocationId2}. If it was charged, its response cannot be recovered or regenerated.`;
|
|
2128
|
+
}
|
|
2129
|
+
var CHARGED_UNDELIVERED = /* @__PURE__ */ new Set([
|
|
2130
|
+
"result_too_large",
|
|
2131
|
+
"response_rejected",
|
|
2132
|
+
"invalid_response",
|
|
2133
|
+
"response_hook_failed"
|
|
2134
|
+
]);
|
|
2135
|
+
function purchaseFailureNotice(failure) {
|
|
2136
|
+
if (failure.pending) {
|
|
2137
|
+
return {
|
|
2138
|
+
level: "warning",
|
|
2139
|
+
text: uncertainPurchaseMessage(failure.invocationId || "(unknown)")
|
|
2140
|
+
};
|
|
2141
|
+
}
|
|
2142
|
+
const charged = failure.payment?.chargedAtomic;
|
|
2143
|
+
if (charged !== void 0 || CHARGED_UNDELIVERED.has(failure.code)) {
|
|
2144
|
+
return {
|
|
2145
|
+
level: "error",
|
|
2146
|
+
text: `This turn was paid${charged !== void 0 ? ` (${formatUsdc(charged)})` : ""} but its response was lost (${failure.code}). ${LOST_RESPONSE_TEXT}`
|
|
2147
|
+
};
|
|
2148
|
+
}
|
|
2149
|
+
return void 0;
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
// src/lib/agent/budget-ui.ts
|
|
2153
|
+
var BUDGET_STATUS_KEY = "xgate-taskmarket";
|
|
2154
|
+
function isBudgetView(value) {
|
|
2155
|
+
const view = value;
|
|
2156
|
+
return !!view && view.version === 1 && Array.isArray(view.channels) && Array.isArray(view.unresolved) && Array.isArray(view.allocations) && typeof view.session === "object";
|
|
2157
|
+
}
|
|
2158
|
+
function errorCode(error) {
|
|
2159
|
+
const code = error?.code;
|
|
2160
|
+
return typeof code === "string" ? code : "controller_unavailable";
|
|
2161
|
+
}
|
|
2162
|
+
function withPurchaseNotices(transport, notify) {
|
|
2163
|
+
const wrapped = {
|
|
2164
|
+
models: (signal) => transport.models(signal),
|
|
2165
|
+
async purchase(invocation, signal, onResponse) {
|
|
2166
|
+
try {
|
|
2167
|
+
return await transport.purchase(invocation, signal, onResponse);
|
|
2168
|
+
} catch (error) {
|
|
2169
|
+
const failure = error;
|
|
2170
|
+
if (typeof failure?.pending === "boolean" && typeof failure.code === "string") {
|
|
2171
|
+
const notice = purchaseFailureNotice({
|
|
2172
|
+
code: failure.code,
|
|
2173
|
+
invocationId: typeof failure.invocationId === "string" && failure.invocationId ? failure.invocationId : invocation.id,
|
|
2174
|
+
pending: failure.pending,
|
|
2175
|
+
...failure.payment ? { payment: failure.payment } : {}
|
|
2176
|
+
});
|
|
2177
|
+
if (notice) notify(notice.text, notice.level);
|
|
2178
|
+
}
|
|
2179
|
+
throw error;
|
|
2180
|
+
}
|
|
2181
|
+
}
|
|
2182
|
+
};
|
|
2183
|
+
if (transport.status) wrapped.status = (signal) => transport.status(signal);
|
|
2184
|
+
if (transport.receipts) wrapped.receipts = (signal) => transport.receipts(signal);
|
|
2185
|
+
if (transport.reconcile) {
|
|
2186
|
+
wrapped.reconcile = (id, signal) => transport.reconcile(id, signal);
|
|
2187
|
+
}
|
|
2188
|
+
return wrapped;
|
|
2189
|
+
}
|
|
2190
|
+
function isOffer(value) {
|
|
2191
|
+
const offer = value;
|
|
2192
|
+
return !!offer && typeof offer.usdPerCredit === "string" && Array.isArray(offer.denominations) && offer.denominations.every(
|
|
2193
|
+
(entry) => Number.isSafeInteger(entry?.amountUsd) && typeof entry?.credits === "string"
|
|
2194
|
+
);
|
|
2195
|
+
}
|
|
2196
|
+
function registerBudgetUi(pi2, channel) {
|
|
2197
|
+
let context;
|
|
2198
|
+
let inFlight;
|
|
2199
|
+
const read = async () => {
|
|
2200
|
+
if (!channel) throw Object.assign(new Error("unavailable"), { code: "controller_unavailable" });
|
|
2201
|
+
const value = await channel.budget();
|
|
2202
|
+
if (!isBudgetView(value)) throw Object.assign(new Error("invalid"), { code: "invalid_budget" });
|
|
2203
|
+
return value;
|
|
2204
|
+
};
|
|
2205
|
+
const refresh = (ctx) => {
|
|
2206
|
+
if (ctx) context = ctx;
|
|
2207
|
+
inFlight ??= read().then((view) => {
|
|
2208
|
+
context?.ui.setStatus(BUDGET_STATUS_KEY, statusBarText(view));
|
|
2209
|
+
return view;
|
|
2210
|
+
}).catch(() => {
|
|
2211
|
+
context?.ui.setStatus(BUDGET_STATUS_KEY, "budget unavailable");
|
|
2212
|
+
return void 0;
|
|
2213
|
+
}).finally(() => {
|
|
2214
|
+
inFlight = void 0;
|
|
2215
|
+
});
|
|
2216
|
+
return inFlight;
|
|
2217
|
+
};
|
|
2218
|
+
pi2.on("session_start", async (_event, ctx) => {
|
|
2219
|
+
await refresh(ctx);
|
|
2220
|
+
});
|
|
2221
|
+
pi2.on("turn_end", async (_event, ctx) => {
|
|
2222
|
+
await refresh(ctx);
|
|
2223
|
+
});
|
|
2224
|
+
pi2.registerCommand("budget", {
|
|
2225
|
+
description: "Show what this session cost, the channel (locked, charged, pending vouchers, refundable), allocations, unresolved purchases and compute credit",
|
|
2226
|
+
handler: async (_args, ctx) => {
|
|
2227
|
+
context = ctx;
|
|
2228
|
+
try {
|
|
2229
|
+
const view = await read();
|
|
2230
|
+
ctx.ui.setStatus(BUDGET_STATUS_KEY, statusBarText(view));
|
|
2231
|
+
ctx.ui.notify(budgetLines(view).join("\n"), "info");
|
|
2232
|
+
} catch (error) {
|
|
2233
|
+
ctx.ui.notify(`The budget is unavailable (${errorCode(error)})`, "error");
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
});
|
|
2237
|
+
pi2.registerCommand("credits", {
|
|
2238
|
+
description: "Show compute credit and buy a chunk from your compute allocation (asks first; /credits <usd> picks the size)",
|
|
2239
|
+
handler: async (args, ctx) => {
|
|
2240
|
+
context = ctx;
|
|
2241
|
+
if (!channel) {
|
|
2242
|
+
ctx.ui.notify("Compute credit is unavailable (controller_unavailable)", "error");
|
|
2243
|
+
return;
|
|
2244
|
+
}
|
|
2245
|
+
try {
|
|
2246
|
+
const view = await read();
|
|
2247
|
+
if (view.compute) ctx.ui.notify(computeLines(view.compute).join("\n"), "info");
|
|
2248
|
+
if (view.compute && !view.compute.configured) return;
|
|
2249
|
+
const offer = await channel.creditOffer();
|
|
2250
|
+
if (!isOffer(offer) || offer.denominations.length === 0) {
|
|
2251
|
+
ctx.ui.notify("Compute credit is not on sale right now", "error");
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
const requested = args.trim().replace(/^\$/, "");
|
|
2255
|
+
const size = requested ? offer.denominations.find((entry) => String(entry.amountUsd) === requested) : offer.denominations.find((entry) => entry.default) ?? offer.denominations[0];
|
|
2256
|
+
if (!size) {
|
|
2257
|
+
ctx.ui.notify(
|
|
2258
|
+
`Compute credit is sold in $${offer.denominations.map((entry) => entry.amountUsd).join(", $")} chunks`,
|
|
2259
|
+
"error"
|
|
2260
|
+
);
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
if (!ctx.hasUI) {
|
|
2264
|
+
ctx.ui.notify("Buying compute credit needs the interactive session", "error");
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2267
|
+
const approved = await ctx.ui.confirm(
|
|
2268
|
+
"Buy compute credit",
|
|
2269
|
+
`Buy $${size.amountUsd} of compute credit (${size.credits} credits at $${offer.usdPerCredit} each) in USDC from your compute allocation? Its daily and monthly caps apply.`
|
|
2270
|
+
);
|
|
2271
|
+
if (!approved) return;
|
|
2272
|
+
const bought = await channel.buyCredits(size.amountUsd);
|
|
2273
|
+
ctx.ui.notify(
|
|
2274
|
+
`Bought ${bought?.purchase?.credits ?? size.credits} credits; ${bought?.balance?.availableCredits ?? "?"} available`,
|
|
2275
|
+
"info"
|
|
2276
|
+
);
|
|
2277
|
+
await refresh(ctx);
|
|
2278
|
+
} catch (error) {
|
|
2279
|
+
const code = errorCode(error);
|
|
2280
|
+
ctx.ui.notify(
|
|
2281
|
+
code === "purchase_pending" ? "Nothing was bought: an earlier compute purchase may have been paid and is not settled yet. Run taskmarket agent credits reconcile; it never pays again." : code === "controller_unavailable" || code === "aborted" ? "The controller did not answer, so the purchase may still complete. It is never repeated automatically; check with /budget or taskmarket agent credits status." : `Nothing was bought (${code})`,
|
|
2282
|
+
"error"
|
|
2283
|
+
);
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
2286
|
+
});
|
|
2287
|
+
return { refresh };
|
|
2288
|
+
}
|
|
2289
|
+
|
|
2009
2290
|
// src/lib/agent/inference-channel.ts
|
|
2010
2291
|
init_cjs_shims();
|
|
2011
2292
|
var import_net = __toESM(require("net"), 1);
|
|
2012
2293
|
var MAX_MESSAGE_BYTES = 48 * 1024 * 1024;
|
|
2013
2294
|
var VIEW_TIMEOUT_MS = 6e4;
|
|
2295
|
+
var BUY_TIMEOUT_MS = 5 * 6e4;
|
|
2014
2296
|
var CODE = /^[a-z0-9_]{1,64}$/;
|
|
2015
2297
|
function lines(stream, onLine, onOverflow, maxBytes) {
|
|
2016
2298
|
let chunks = [];
|
|
@@ -2147,8 +2429,8 @@ var ChannelXgateTransport = class _ChannelXgateTransport {
|
|
|
2147
2429
|
this.requests.write(line);
|
|
2148
2430
|
return { id, reply };
|
|
2149
2431
|
}
|
|
2150
|
-
async view(body, signal) {
|
|
2151
|
-
const sent = this.send(body, { timeoutMs
|
|
2432
|
+
async view(body, signal, timeoutMs = VIEW_TIMEOUT_MS) {
|
|
2433
|
+
const sent = this.send(body, { timeoutMs, signal });
|
|
2152
2434
|
if (sent === "too_large") throw new XgateError("message_too_large");
|
|
2153
2435
|
if (!sent) throw new XgateError("controller_unavailable");
|
|
2154
2436
|
let reply;
|
|
@@ -2211,6 +2493,25 @@ var ChannelXgateTransport = class _ChannelXgateTransport {
|
|
|
2211
2493
|
async reconcile(invocationId2, signal) {
|
|
2212
2494
|
return (await this.view({ op: "reconcile", invocationId: invocationId2 }, signal)).value;
|
|
2213
2495
|
}
|
|
2496
|
+
/** The controller's budget view (`BudgetView`) for the status bar and `/budget`. */
|
|
2497
|
+
async budget(signal) {
|
|
2498
|
+
return (await this.view({ op: "budget" }, signal)).value;
|
|
2499
|
+
}
|
|
2500
|
+
/** The compute credit sizes and price the controller can buy, for `/credits`. */
|
|
2501
|
+
async creditOffer(signal) {
|
|
2502
|
+
return (await this.view({ op: "credit_offer" }, signal)).value;
|
|
2503
|
+
}
|
|
2504
|
+
/**
|
|
2505
|
+
* `/credits`: ask the controller to buy one chunk of compute credit. The controller's purchase
|
|
2506
|
+
* is journaled and capped; a reply lost here never makes it buy again.
|
|
2507
|
+
*/
|
|
2508
|
+
async buyCredits(amountUsd, signal) {
|
|
2509
|
+
return (await this.view(
|
|
2510
|
+
{ op: "buy_credits", ...amountUsd !== void 0 ? { amountUsd } : {} },
|
|
2511
|
+
signal,
|
|
2512
|
+
BUY_TIMEOUT_MS
|
|
2513
|
+
)).value;
|
|
2514
|
+
}
|
|
2214
2515
|
};
|
|
2215
2516
|
function unavailableTransport() {
|
|
2216
2517
|
return {
|
|
@@ -2236,20 +2537,24 @@ function openTransport() {
|
|
|
2236
2537
|
}
|
|
2237
2538
|
async function taskmarketXgate(pi2) {
|
|
2238
2539
|
const transport = openTransport();
|
|
2239
|
-
|
|
2540
|
+
const channel = transport instanceof ChannelXgateTransport ? transport : void 0;
|
|
2240
2541
|
let context;
|
|
2241
2542
|
let controllerGone = false;
|
|
2242
2543
|
pi2.on("session_start", async (_event, ctx) => {
|
|
2243
2544
|
context = ctx;
|
|
2244
2545
|
if (controllerGone) ctx.shutdown();
|
|
2245
2546
|
});
|
|
2246
|
-
if (
|
|
2247
|
-
|
|
2547
|
+
if (channel) {
|
|
2548
|
+
channel.onClose((reason) => {
|
|
2248
2549
|
log(`The taskmarket controller is gone (${reason}); ending the session`);
|
|
2249
2550
|
controllerGone = true;
|
|
2250
2551
|
context?.shutdown();
|
|
2251
2552
|
});
|
|
2252
2553
|
}
|
|
2554
|
+
await createXgateExtension({
|
|
2555
|
+
transport: withPurchaseNotices(transport, (text2, level) => context?.ui.notify(text2, level))
|
|
2556
|
+
})(pi2);
|
|
2557
|
+
registerBudgetUi(pi2, channel);
|
|
2253
2558
|
pi2.on("model_select", async (event, ctx) => {
|
|
2254
2559
|
if (event.model.provider === "xgate") return;
|
|
2255
2560
|
const fallback = event.previousModel?.provider === "xgate" ? event.previousModel : ctx.modelRegistry.getAvailable().find((model) => model.provider === "xgate");
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { createRequire as __tmCreateRequire } from 'node:module'; import { fileURLToPath as __tmFileURLToPath } from 'node:url'; import { dirname as __tmDirname } from 'node:path'; const require = __tmCreateRequire(import.meta.url); const __tmFilename = __tmFileURLToPath(import.meta.url); const __tmDirnameValue = __tmDirname(__tmFilename);
|
|
2
|
+
import {
|
|
3
|
+
openAgentChannels
|
|
4
|
+
} from "./chunk-GJQUALXN.js";
|
|
5
|
+
import {
|
|
6
|
+
X402_INVOCATION_ID_PATTERN
|
|
7
|
+
} from "./chunk-FRV3ZHUR.js";
|
|
8
|
+
import {
|
|
9
|
+
AgentLaunchError
|
|
10
|
+
} from "./chunk-YM4S6IE4.js";
|
|
11
|
+
import {
|
|
12
|
+
AGENT_VIEW_VERSION,
|
|
13
|
+
LOST_RESPONSE_TEXT,
|
|
14
|
+
formatUsdc
|
|
15
|
+
} from "./chunk-73RXOYME.js";
|
|
16
|
+
import "./chunk-S7SJMJ2I.js";
|
|
17
|
+
|
|
18
|
+
// src/lib/agent/reconcile.ts
|
|
19
|
+
function outcomeOf(status) {
|
|
20
|
+
if (status.state === "in_progress") return "in_progress";
|
|
21
|
+
if (status.charge === "manually_resolved") return "resolved_manually";
|
|
22
|
+
if (status.uncertain) return "uncertain";
|
|
23
|
+
if (status.charge === "settled") return "charged";
|
|
24
|
+
if (status.charge === "none" || status.charge === "released") return "not_charged";
|
|
25
|
+
return "uncertain";
|
|
26
|
+
}
|
|
27
|
+
function messageFor(outcome, status) {
|
|
28
|
+
const charged = status.payment?.settledAmount;
|
|
29
|
+
switch (outcome) {
|
|
30
|
+
case "charged":
|
|
31
|
+
return `Charged ${charged !== void 0 ? formatUsdc(charged) : "an amount the router committed"}. If its response did not reach you: ${LOST_RESPONSE_TEXT}`;
|
|
32
|
+
case "not_charged":
|
|
33
|
+
return "Nothing was charged. The request is not sent again; ask again in the agent if you still want an answer.";
|
|
34
|
+
case "in_progress":
|
|
35
|
+
return "It is still being sent by another process; reconcile it again once that finishes.";
|
|
36
|
+
case "resolved_manually":
|
|
37
|
+
return "It was resolved by hand; its recorded amount stands.";
|
|
38
|
+
default:
|
|
39
|
+
return "Its outcome is still unknown. It keeps its maximum counted and blocks new turns on its channel; reconcile it again later. It is never sent again.";
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
async function railFor(rails, invocationId) {
|
|
43
|
+
for (const rail of rails) {
|
|
44
|
+
try {
|
|
45
|
+
await rail.payer.status(invocationId);
|
|
46
|
+
return rail;
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (error.code !== "invocation_not_found") throw error;
|
|
49
|
+
}
|
|
50
|
+
const lease = (await rail.payer.channelStatus()).pendingOperation;
|
|
51
|
+
if (lease?.kind === "payment" && lease.id === invocationId) return rail;
|
|
52
|
+
}
|
|
53
|
+
throw new AgentLaunchError(
|
|
54
|
+
"invocation_not_found",
|
|
55
|
+
`No agent purchase ${invocationId} is recorded on this device`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
async function reconcileInvocation(invocationId, deps = {}) {
|
|
59
|
+
if (!X402_INVOCATION_ID_PATTERN.test(invocationId)) {
|
|
60
|
+
throw new AgentLaunchError("invalid_invocation_id", `Not an invocation ID: ${invocationId}`);
|
|
61
|
+
}
|
|
62
|
+
const opened = await (deps.openChannels ?? openAgentChannels)();
|
|
63
|
+
const rail = await railFor([...opened.rails, ...opened.retired ?? []], invocationId);
|
|
64
|
+
const status = await rail.payer.reconcile(invocationId);
|
|
65
|
+
const lease = (await rail.payer.channelStatus()).pendingOperation;
|
|
66
|
+
const outcome = outcomeOf(status);
|
|
67
|
+
return {
|
|
68
|
+
version: AGENT_VIEW_VERSION,
|
|
69
|
+
kind: "taskmarket.agent.reconcile",
|
|
70
|
+
invocationId,
|
|
71
|
+
rail: rail.rail.id,
|
|
72
|
+
outcome,
|
|
73
|
+
state: status.state,
|
|
74
|
+
charge: status.charge,
|
|
75
|
+
authorizedAtomic: status.payment?.authorizedAmount ?? null,
|
|
76
|
+
chargedAtomic: status.payment?.settledAmount ?? null,
|
|
77
|
+
blocksChannel: lease?.kind === "payment" && lease.id === invocationId,
|
|
78
|
+
message: messageFor(outcome, status)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
export {
|
|
82
|
+
reconcileInvocation
|
|
83
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { createRequire as __tmCreateRequire } from 'node:module'; import { fileURLToPath as __tmFileURLToPath } from 'node:url'; import { dirname as __tmDirname } from 'node:path'; const require = __tmCreateRequire(import.meta.url); const __tmFilename = __tmFileURLToPath(import.meta.url); const __tmDirnameValue = __tmDirname(__tmFilename);
|
|
2
|
+
import {
|
|
3
|
+
collectAgentStatus,
|
|
4
|
+
listSandboxes,
|
|
5
|
+
statusLines
|
|
6
|
+
} from "./chunk-XXD7TWLP.js";
|
|
7
|
+
import "./chunk-LDCFKFF6.js";
|
|
8
|
+
import "./chunk-52Q2DCZN.js";
|
|
9
|
+
import "./chunk-GJQUALXN.js";
|
|
10
|
+
import "./chunk-FRV3ZHUR.js";
|
|
11
|
+
import "./chunk-YM4S6IE4.js";
|
|
12
|
+
import "./chunk-73RXOYME.js";
|
|
13
|
+
import "./chunk-S7SJMJ2I.js";
|
|
14
|
+
export {
|
|
15
|
+
collectAgentStatus,
|
|
16
|
+
listSandboxes,
|
|
17
|
+
statusLines
|
|
18
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lucid-agents/taskmarket",
|
|
3
|
-
"version": "1.17.
|
|
3
|
+
"version": "1.17.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"taskmarket": "./dist/index.js"
|
|
@@ -28,11 +28,11 @@
|
|
|
28
28
|
"tsx": "^4.7.0",
|
|
29
29
|
"typescript": "^5.3.3",
|
|
30
30
|
"vitest": "^2.0.0",
|
|
31
|
+
"@taskmarket/agent-host": "0.0.1",
|
|
31
32
|
"@taskmarket/contracts": "0.0.0",
|
|
32
|
-
"@taskmarket/prettier-config": "0.0.0",
|
|
33
33
|
"@taskmarket/eslint-config": "0.0.0",
|
|
34
|
+
"@taskmarket/prettier-config": "0.0.0",
|
|
34
35
|
"@taskmarket/remote-tools": "0.0.1",
|
|
35
|
-
"@taskmarket/agent-host": "0.0.1",
|
|
36
36
|
"@taskmarket/shared": "2.0.0",
|
|
37
37
|
"@taskmarket/x402-payer": "0.1.0"
|
|
38
38
|
},
|