@talgent/computer 0.1.2 → 0.2.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.
Files changed (2) hide show
  1. package/dist/computer.mjs +250 -137
  2. package/package.json +1 -1
package/dist/computer.mjs CHANGED
@@ -4076,7 +4076,7 @@ function num(value) {
4076
4076
 
4077
4077
  // src/run.ts
4078
4078
  import { homedir as homedir2 } from "node:os";
4079
- import { join as join14 } from "node:path";
4079
+ import { join as join15 } from "node:path";
4080
4080
 
4081
4081
  // src/claude-code.ts
4082
4082
  import { execFile } from "node:child_process";
@@ -5566,9 +5566,10 @@ var HEADER_VALUE = /^[\x21-\x7e]+$/;
5566
5566
  var LOOPBACK = /* @__PURE__ */ new Set(["127.0.0.1", "localhost", "[::1]"]);
5567
5567
  function parseCredentials(body) {
5568
5568
  if (!isObject(body)) throw new InvalidCredentialsError("body must be an object");
5569
- const { model, gateway } = body;
5569
+ const { model, gateway, api } = body;
5570
5570
  const result = { gateway: parseGateway(gateway) };
5571
5571
  if (model !== void 0 && model !== null) result.model = parseModel(model);
5572
+ if (api !== void 0 && api !== null) result.api = parseApi(api, result.gateway.url);
5572
5573
  return result;
5573
5574
  }
5574
5575
  function parseModel(model) {
@@ -5579,6 +5580,12 @@ function parseModel(model) {
5579
5580
  }
5580
5581
  return { baseUrl: url.href, key: headerValue(model.key, "model.key") };
5581
5582
  }
5583
+ function parseApi(api, gatewayUrl) {
5584
+ if (!isObject(api)) throw new InvalidCredentialsError("api must be an object");
5585
+ const url = parseUrl(api.url, "api.url");
5586
+ if (url.origin !== new URL(gatewayUrl).origin) throw new InvalidCredentialsError("api.url must lie on gateway.url's origin");
5587
+ return { url: url.href };
5588
+ }
5582
5589
  function parseGateway(gateway) {
5583
5590
  if (!isObject(gateway)) throw new InvalidCredentialsError("gateway must be an object");
5584
5591
  const url = parseUrl(gateway.url, "gateway.url");
@@ -5924,12 +5931,12 @@ var Works = class {
5924
5931
  }
5925
5932
  // Opens or updates the Work's environment and answers its root.
5926
5933
  async put(workId, body) {
5927
- const { accessToken, environment, modelProxy, telemetry } = parseEnvironment(body);
5934
+ const { accessToken, environment, modelProxy, platformApi, telemetry } = parseEnvironment(body);
5928
5935
  const root = this.root(workId);
5929
5936
  await mkdir6(root, { recursive: true, mode: 448 });
5930
5937
  await chmod5(root, 448);
5931
5938
  await mkdir6(this.recordsDir, { recursive: true, mode: 448 });
5932
- const stored = { tokenSha256: sha2562(accessToken), environment, modelProxy, telemetry };
5939
+ const stored = { tokenSha256: sha2562(accessToken), environment, modelProxy, platformApi, telemetry };
5933
5940
  writeJsonAtomic(join9(this.recordsDir, `${workId}.json`), stored);
5934
5941
  this.cache.set(workId, stored);
5935
5942
  return root;
@@ -5968,7 +5975,7 @@ var Works = class {
5968
5975
  const expected = Buffer.from(stored.tokenSha256, "hex");
5969
5976
  const presented = Buffer.from(sha2562(token), "hex");
5970
5977
  if (token === "" || !timingSafeEqual(expected, presented)) return { kind: "denied" };
5971
- const work = { root: this.root(workId), environment: stored.environment, modelProxy: stored.modelProxy === true };
5978
+ const work = { root: this.root(workId), environment: stored.environment, modelProxy: stored.modelProxy, platformApi: stored.platformApi === true };
5972
5979
  if (stored.telemetry !== void 0) work.telemetry = stored.telemetry;
5973
5980
  return { kind: "ok", work };
5974
5981
  }
@@ -5989,9 +5996,10 @@ var Works = class {
5989
5996
  };
5990
5997
  function parseEnvironment(body) {
5991
5998
  if (typeof body !== "object" || body === null || Array.isArray(body)) throw new InvalidEnvironmentError("body must be an object");
5992
- const { access_token: accessToken, environment = {}, model_proxy: modelProxy = false, telemetry: rawTelemetry } = body;
5999
+ const { access_token: accessToken, environment = {}, model_proxy: modelProxy = false, platform_api: platformApi = false, telemetry: rawTelemetry } = body;
5993
6000
  if (typeof accessToken !== "string" || accessToken.trim() === "") throw new InvalidEnvironmentError("access_token required");
5994
6001
  if (typeof modelProxy !== "boolean") throw new InvalidEnvironmentError("model_proxy must be a boolean");
6002
+ if (typeof platformApi !== "boolean") throw new InvalidEnvironmentError("platform_api must be a boolean");
5995
6003
  if (typeof environment !== "object" || environment === null || Array.isArray(environment)) {
5996
6004
  throw new InvalidEnvironmentError("environment must be an object");
5997
6005
  }
@@ -6009,7 +6017,7 @@ function parseEnvironment(body) {
6009
6017
  if (error instanceof InvalidTelemetryError) throw new InvalidEnvironmentError(error.message);
6010
6018
  throw error;
6011
6019
  }
6012
- return { accessToken, environment: result, modelProxy, telemetry };
6020
+ return { accessToken, environment: result, modelProxy, platformApi, telemetry };
6013
6021
  }
6014
6022
  async function removeTree(path) {
6015
6023
  try {
@@ -6487,8 +6495,10 @@ var ExecdService = class {
6487
6495
  }
6488
6496
  // The machine's environment, then the Work's, then what Talgent Computer
6489
6497
  // owns: the machine's HOME, the Work root, the bundled Claude Code first on
6490
- // PATH, the run's way to the local proxy and, when the platform named it,
6491
- // where its telemetry goes, in place of the machine's. A Work on a Model
6498
+ // PATH, the run's way to the local proxy and, when the platform named them,
6499
+ // the platform's native API through the proxy and where its telemetry goes,
6500
+ // in place of the machine's. The API address carries the run's token in
6501
+ // its path, as a plain HTTP client presents nothing else. A Work on a Model
6492
6502
  // Configuration reaches its model only through the proxy, with the run's
6493
6503
  // token, whether or not its credentials were delivered yet: it never falls
6494
6504
  // back to the machine's own login.
@@ -6496,12 +6506,14 @@ var ExecdService = class {
6496
6506
  const { baseEnv, home, claudeBinDir: bin, proxyOrigin, platform = process.platform } = this.options;
6497
6507
  const proxyUrl = `${proxyOrigin()}/w/${workId}`;
6498
6508
  const model = work.modelProxy ? { ANTHROPIC_BASE_URL: `${proxyUrl}/model`, ANTHROPIC_AUTH_TOKEN: proxyToken } : {};
6509
+ const api = work.platformApi ? { TALGENT_API_ADDRESS: `${proxyUrl}/api/${proxyToken}` } : {};
6499
6510
  const env = mergeEnvironment(platform, baseEnv, work.environment, {
6500
6511
  HOME: home,
6501
6512
  TALGENT_WORK_ROOT: work.root,
6502
6513
  TALGENT_PROXY_URL: proxyUrl,
6503
6514
  TALGENT_PROXY_TOKEN: proxyToken,
6504
- ...model
6515
+ ...model,
6516
+ ...api
6505
6517
  });
6506
6518
  if (bin !== void 0) prependPath(env, bin, platform);
6507
6519
  if (work.modelProxy) deleteVariable(env, "ANTHROPIC_API_KEY", platform);
@@ -6862,42 +6874,7 @@ var ContentHandles = class {
6862
6874
  // src/proxy/content-links.ts
6863
6875
  import { Transform, pipeline as pipeline2 } from "node:stream";
6864
6876
 
6865
- // src/proxy/publication.ts
6866
- var MAX_SOURCE_BYTES = 64 * 1024 * 1024;
6867
- var MAX_REQUEST_BYTES = base64Length(MAX_SOURCE_BYTES) + 1024 * 1024;
6868
- var TOOLS = /* @__PURE__ */ new Set(["publish_artifact", "replace_artifact"]);
6869
- var INPUTS_DIR = "inputs";
6870
- var completions = new Limit(2);
6871
- var UNHELD_REQUEST_BYTES = 1024 * 1024;
6872
- var INVALID_PARAMS = -32602;
6873
- var INVALID_REQUEST = -32600;
6874
- var UNREADABLE = "source_path names no readable file in the Work's workspace.";
6875
- var TOO_LARGE = "The source file is larger than 64 MiB. Split it or compress it into a smaller archive.";
6876
- var FROM_INPUTS = "source_path is under inputs/, the Work's read-only inputs. Publish a file outside inputs/.";
6877
- var OVER_REQUEST = "The files published in this request exceed its size limit. Publish them in separate requests.";
6878
- var NOT_FORWARDED = "Not forwarded: a publication in this batch was refused.";
6879
- async function completeRequest(request, root) {
6880
- const encoding = request.headers["content-encoding"];
6881
- if (encoding !== void 0 && encoding !== "identity") return { kind: "pass", body: { chunks: [], rest: request } };
6882
- const head = await readHead(request, UNHELD_REQUEST_BYTES);
6883
- if (head.rest === void 0) {
6884
- const body = Buffer.concat(head.chunks);
6885
- const calls = publicationCalls(body);
6886
- if (calls === void 0) return { kind: "pass", body: head };
6887
- return completions.run(() => complete(calls, body, root));
6888
- }
6889
- const rest = head.rest;
6890
- return completions.run(async () => {
6891
- const read = head.chunks.reduce((size, chunk) => size + chunk.length, 0);
6892
- const tail = await readHead(rest, MAX_REQUEST_BYTES - read);
6893
- const chunks = [...head.chunks, ...tail.chunks];
6894
- if (tail.rest !== void 0) return { kind: "pass", body: { chunks, rest: tail.rest } };
6895
- const body = Buffer.concat(chunks);
6896
- const calls = publicationCalls(body);
6897
- if (calls === void 0) return { kind: "pass", body: { chunks: [body] } };
6898
- return complete(calls, body, root);
6899
- });
6900
- }
6877
+ // src/proxy/request-body.ts
6901
6878
  var BODY_IDLE_MS = 3e4;
6902
6879
  function readHead(stream, limit, idleMs = BODY_IDLE_MS) {
6903
6880
  return new Promise((resolve3, reject) => {
@@ -6948,71 +6925,6 @@ function readHead(stream, limit, idleMs = BODY_IDLE_MS) {
6948
6925
  stream.resume();
6949
6926
  });
6950
6927
  }
6951
- function publicationCalls(body) {
6952
- let parsed;
6953
- try {
6954
- parsed = JSON.parse(body.toString("utf8"));
6955
- } catch {
6956
- return void 0;
6957
- }
6958
- const batch = Array.isArray(parsed);
6959
- const messages = batch ? parsed : [parsed];
6960
- const calls = messages.flatMap((message2) => {
6961
- const args = argsOf(message2);
6962
- return args === void 0 ? [] : [{ message: message2, args }];
6963
- });
6964
- return calls.length === 0 ? void 0 : { parsed, batch, messages, calls };
6965
- }
6966
- async function complete({ parsed, batch, messages, calls }, body, root) {
6967
- let size = body.length;
6968
- for (const { message: message2, args } of calls) {
6969
- delete args.source_content;
6970
- if (typeof args.source_path !== "string") continue;
6971
- const source = await readSource(root, args.source_path, MAX_REQUEST_BYTES - size);
6972
- if ("refusal" in source) return { kind: "answer", body: refusal(messages, batch, message2, source.refusal) };
6973
- const content = source.content.toString("base64");
6974
- args.source_content = content;
6975
- size += content.length;
6976
- }
6977
- return { kind: "forward", body: Buffer.from(JSON.stringify(parsed)) };
6978
- }
6979
- function argsOf(message2) {
6980
- if (!isObject(message2) || message2.method !== "tools/call" || !isObject(message2.params)) return void 0;
6981
- const { name, arguments: args } = message2.params;
6982
- return typeof name === "string" && TOOLS.has(name) && isObject(args) ? args : void 0;
6983
- }
6984
- async function readSource(root, sourcePath, budget) {
6985
- if (sourcePath === "") return { refusal: UNREADABLE };
6986
- const file = await openWorkFile(root, workspaceRelative(sourcePath) ?? sourcePath).catch(() => ({ status: 500 }));
6987
- if ("status" in file) return { refusal: UNREADABLE };
6988
- try {
6989
- const inputs = await confined(root, INPUTS_DIR);
6990
- if (inputs !== void 0 && within(inputs, file.path)) return { refusal: FROM_INPUTS };
6991
- if (file.size > MAX_SOURCE_BYTES) return { refusal: TOO_LARGE };
6992
- if (base64Length(file.size) > budget) return { refusal: OVER_REQUEST };
6993
- const content = await readBounded(file.handle, MAX_SOURCE_BYTES);
6994
- if (content === void 0) return { refusal: TOO_LARGE };
6995
- if (base64Length(content.length) > budget) return { refusal: OVER_REQUEST };
6996
- return { content };
6997
- } catch {
6998
- return { refusal: UNREADABLE };
6999
- } finally {
7000
- await file.handle.close().catch(() => {
7001
- });
7002
- }
7003
- }
7004
- function refusal(messages, batch, refused, reason2) {
7005
- const answers = messages.flatMap((message2) => {
7006
- if (!isObject(message2) || typeof message2.method !== "string" || !("id" in message2)) return [];
7007
- const error = message2 === refused ? { code: INVALID_PARAMS, message: reason2 } : { code: INVALID_REQUEST, message: NOT_FORWARDED };
7008
- return [{ jsonrpc: "2.0", id: message2.id, error }];
7009
- });
7010
- if (answers.length === 0) return void 0;
7011
- return Buffer.from(JSON.stringify(batch ? answers : answers[0]));
7012
- }
7013
- function base64Length(bytes) {
7014
- return 4 * Math.ceil(bytes / 3);
7015
- }
7016
6928
 
7017
6929
  // src/proxy/content-links.ts
7018
6930
  var CONTENT_PATH = "/computer/v1/storage/content";
@@ -7094,9 +7006,170 @@ function escapeRegExp2(text2) {
7094
7006
  return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7095
7007
  }
7096
7008
 
7009
+ // src/proxy/publication-source.ts
7010
+ import { lstat as lstat2, realpath as realpath3 } from "node:fs/promises";
7011
+ import { isAbsolute as isAbsolute2, join as join11, normalize, relative } from "node:path";
7012
+ var MAX_SOURCE_BYTES = 64 * 1024 * 1024;
7013
+ var MAX_REQUEST_BYTES = base64Length(MAX_SOURCE_BYTES) + 1024 * 1024;
7014
+ var UNHELD_REQUEST_BYTES = 1024 * 1024;
7015
+ var RESERVED_DIRS = ["inputs", "knowledge"];
7016
+ var completions = new Limit(2);
7017
+ function holdingCompletion(task) {
7018
+ return completions.run(task);
7019
+ }
7020
+ var noFile = (field) => `${field} names no file in the Work's workspace.`;
7021
+ var notOneFile = (field) => `${field} is not one regular file in the Work's workspace, reached without symbolic links.`;
7022
+ var TOO_LARGE = "The source file is larger than 64 MiB. Split it or compress it into a smaller archive.";
7023
+ var reserved = (field, dir) => `${field} is under ${dir}/, which the Work only reads. Publish a file outside inputs/ and knowledge/.`;
7024
+ var OVER_REQUEST = "The files published in this request exceed its size limit. Publish them in separate requests.";
7025
+ async function readSource(root, sourcePath, field, budget) {
7026
+ const invalid = (refusal2) => ({ refusal: refusal2, missing: false });
7027
+ if (sourcePath === "" || sourcePath.includes("\0")) return invalid(notOneFile(field));
7028
+ let rootReal;
7029
+ try {
7030
+ rootReal = await realpath3(root);
7031
+ } catch {
7032
+ return invalid(notOneFile(field));
7033
+ }
7034
+ const target = workspaceRelative(sourcePath) ?? sourcePath;
7035
+ const named = isAbsolute2(target) ? normalize(target) : join11(rootReal, target);
7036
+ const lexical = within(root, named) ? join11(rootReal, relative(root, named)) : named;
7037
+ if (!within(rootReal, lexical) || lexical === rootReal) return invalid(notOneFile(field));
7038
+ const file = await openWorkFile(root, lexical).catch(() => ({ status: 500 }));
7039
+ if ("status" in file) {
7040
+ const missing = file.status === 404 && await absent(lexical);
7041
+ return missing ? { refusal: noFile(field), missing: true } : invalid(notOneFile(field));
7042
+ }
7043
+ try {
7044
+ if (file.path !== lexical) return invalid(notOneFile(field));
7045
+ for (const dir of RESERVED_DIRS) {
7046
+ if (within(join11(rootReal, dir), file.path)) return invalid(reserved(field, dir));
7047
+ }
7048
+ if (file.size > MAX_SOURCE_BYTES) return invalid(TOO_LARGE);
7049
+ if (base64Length(file.size) > budget) return invalid(OVER_REQUEST);
7050
+ const content = await readBounded(file.handle, MAX_SOURCE_BYTES);
7051
+ if (content === void 0) return invalid(TOO_LARGE);
7052
+ if (base64Length(content.length) > budget) return invalid(OVER_REQUEST);
7053
+ return { content };
7054
+ } catch {
7055
+ return invalid(notOneFile(field));
7056
+ } finally {
7057
+ await file.handle.close().catch(() => {
7058
+ });
7059
+ }
7060
+ }
7061
+ async function absent(path) {
7062
+ try {
7063
+ await lstat2(path);
7064
+ return false;
7065
+ } catch {
7066
+ return true;
7067
+ }
7068
+ }
7069
+ function base64Length(bytes) {
7070
+ return 4 * Math.ceil(bytes / 3);
7071
+ }
7072
+
7073
+ // src/proxy/publication.ts
7074
+ var TOOLS = /* @__PURE__ */ new Set(["publish_artifact", "replace_artifact"]);
7075
+ var INVALID_PARAMS = -32602;
7076
+ var INVALID_REQUEST = -32600;
7077
+ var NOT_FORWARDED = "Not forwarded: a publication in this batch was refused.";
7078
+ async function completeRequest(request, root) {
7079
+ const encoding = request.headers["content-encoding"];
7080
+ if (encoding !== void 0 && encoding !== "identity") return { kind: "pass", body: { chunks: [], rest: request } };
7081
+ const head = await readHead(request, UNHELD_REQUEST_BYTES);
7082
+ if (head.rest === void 0) {
7083
+ const body = Buffer.concat(head.chunks);
7084
+ const calls = publicationCalls(body);
7085
+ if (calls === void 0) return { kind: "pass", body: head };
7086
+ return holdingCompletion(() => complete(calls, body, root));
7087
+ }
7088
+ const rest = head.rest;
7089
+ return holdingCompletion(async () => {
7090
+ const read = head.chunks.reduce((size, chunk) => size + chunk.length, 0);
7091
+ const tail = await readHead(rest, MAX_REQUEST_BYTES - read);
7092
+ const chunks = [...head.chunks, ...tail.chunks];
7093
+ if (tail.rest !== void 0) return { kind: "pass", body: { chunks, rest: tail.rest } };
7094
+ const body = Buffer.concat(chunks);
7095
+ const calls = publicationCalls(body);
7096
+ if (calls === void 0) return { kind: "pass", body: { chunks: [body] } };
7097
+ return complete(calls, body, root);
7098
+ });
7099
+ }
7100
+ function publicationCalls(body) {
7101
+ let parsed;
7102
+ try {
7103
+ parsed = JSON.parse(body.toString("utf8"));
7104
+ } catch {
7105
+ return void 0;
7106
+ }
7107
+ const batch = Array.isArray(parsed);
7108
+ const messages = batch ? parsed : [parsed];
7109
+ const calls = messages.flatMap((message2) => {
7110
+ const args = argsOf(message2);
7111
+ return args === void 0 ? [] : [{ message: message2, args }];
7112
+ });
7113
+ return calls.length === 0 ? void 0 : { parsed, batch, messages, calls };
7114
+ }
7115
+ async function complete({ parsed, batch, messages, calls }, body, root) {
7116
+ let size = body.length;
7117
+ for (const { message: message2, args } of calls) {
7118
+ delete args.source_content;
7119
+ if (typeof args.source_path !== "string") continue;
7120
+ const source = await readSource(root, args.source_path, "source_path", MAX_REQUEST_BYTES - size);
7121
+ if ("refusal" in source) return { kind: "answer", body: refusal(messages, batch, message2, source.refusal) };
7122
+ const content = source.content.toString("base64");
7123
+ args.source_content = content;
7124
+ size += content.length;
7125
+ }
7126
+ return { kind: "forward", body: Buffer.from(JSON.stringify(parsed)) };
7127
+ }
7128
+ function argsOf(message2) {
7129
+ if (!isObject(message2) || message2.method !== "tools/call" || !isObject(message2.params)) return void 0;
7130
+ const { name, arguments: args } = message2.params;
7131
+ return typeof name === "string" && TOOLS.has(name) && isObject(args) ? args : void 0;
7132
+ }
7133
+ function refusal(messages, batch, refused, reason2) {
7134
+ const answers = messages.flatMap((message2) => {
7135
+ if (!isObject(message2) || typeof message2.method !== "string" || !("id" in message2)) return [];
7136
+ const error = message2 === refused ? { code: INVALID_PARAMS, message: reason2 } : { code: INVALID_REQUEST, message: NOT_FORWARDED };
7137
+ return [{ jsonrpc: "2.0", id: message2.id, error }];
7138
+ });
7139
+ if (answers.length === 0) return void 0;
7140
+ return Buffer.from(JSON.stringify(batch ? answers : answers[0]));
7141
+ }
7142
+
7143
+ // src/proxy/api-publication.ts
7144
+ var ARTIFACT_COMMANDS = /* @__PURE__ */ new Set([
7145
+ "/talgent.storage.private.v1.ArtifactService/CreateArtifact",
7146
+ "/talgent.storage.private.v1.ArtifactService/ReplaceArtifact"
7147
+ ]);
7148
+ async function completeArtifactCommand(request, root) {
7149
+ const encoding = request.headers["content-encoding"];
7150
+ if (encoding !== void 0 && encoding !== "identity") return { kind: "pass", body: { chunks: [], rest: request } };
7151
+ const head = await readHead(request, UNHELD_REQUEST_BYTES);
7152
+ if (head.rest !== void 0) return { kind: "pass", body: head };
7153
+ const body = Buffer.concat(head.chunks);
7154
+ let message2;
7155
+ try {
7156
+ message2 = JSON.parse(body.toString("utf8"));
7157
+ } catch {
7158
+ return { kind: "pass", body: { chunks: [body] } };
7159
+ }
7160
+ if (!isObject(message2) || typeof message2.path !== "string" || message2.content !== void 0) return { kind: "pass", body: { chunks: [body] } };
7161
+ const { path, ...rest } = message2;
7162
+ return holdingCompletion(async () => {
7163
+ const source = await readSource(root, path, "path", MAX_REQUEST_BYTES - body.length);
7164
+ if ("refusal" in source) return { kind: "refuse", message: source.refusal, missing: source.missing };
7165
+ return { kind: "forward", body: Buffer.from(JSON.stringify({ ...rest, content: source.content.toString("base64") })) };
7166
+ });
7167
+ }
7168
+
7097
7169
  // src/proxy/server.ts
7098
7170
  var KEYED_MODEL_PATHS = /* @__PURE__ */ new Set(["/v1/messages", "/v1/messages/count_tokens"]);
7099
- var ROUTE = /^\/w\/([^/]+)\/(model|mcp|content)(\/.*)?$/;
7171
+ var ROUTE = /^\/w\/([^/]+)\/(model|mcp|content|api)(\/.*)?$/;
7172
+ var API_PATH = /^\/([^/]+)(\/.*)$/;
7100
7173
  var CONTENT_HANDLE = /^\/([A-Za-z0-9_-]+)$/;
7101
7174
  var LocalProxy = class {
7102
7175
  portFile;
@@ -7183,6 +7256,7 @@ var LocalProxy = class {
7183
7256
  if (match === null || !validId(match[1])) return notFound2(response);
7184
7257
  const [, workId, route, rest = ""] = match;
7185
7258
  if (route === "content") return this.content(request, response, workId, rest);
7259
+ if (route === "api") return this.api(request, response, workId, rest, search);
7186
7260
  if (!await this.access.tokenValid(workId, presentedToken(request.headers))) {
7187
7261
  return answerJson(response, 401, { code: "UNAUTHORIZED", message: "present the token of a run of this Work" });
7188
7262
  }
@@ -7223,6 +7297,45 @@ var LocalProxy = class {
7223
7297
  fail: (reason2) => this.log(`local proxy: Work ${workId} ${route} upstream failed: ${reason2}`)
7224
7298
  });
7225
7299
  }
7300
+ // A native API call of a Work's plain HTTP client, such as curl, which
7301
+ // presents the run's token as the first segment of the path. The proxy
7302
+ // adds the Work's identity, and for an Artifact command naming a file of
7303
+ // the Work, the file's bytes.
7304
+ async api(request, response, workId, rest, search) {
7305
+ const match = API_PATH.exec(rest);
7306
+ if (match === null) return notFound2(response);
7307
+ const [, token, path] = match;
7308
+ if (!await this.access.tokenValid(workId, token)) {
7309
+ return answerJson(response, 401, { code: "unauthenticated", message: "present the token of a run of this Work" });
7310
+ }
7311
+ const credentials = await this.access.credentials(workId);
7312
+ if (credentials === void 0) {
7313
+ return answerJson(response, 503, { code: "unavailable", message: "the Work's credentials were not delivered" });
7314
+ }
7315
+ if (credentials.api === void 0) return notFound2(response);
7316
+ const target = below(credentials.api.url, path, search);
7317
+ if (target === void 0) return notFound2(response);
7318
+ const headers = requestHeaders(request.headers);
7319
+ addIdentity(headers, credentials.gateway);
7320
+ let body;
7321
+ if (request.method === "POST" && ARTIFACT_COMMANDS.has(path)) {
7322
+ const completed = await completeArtifactCommand(request, this.access.workRoot(workId));
7323
+ if (completed.kind === "refuse") {
7324
+ if (completed.missing) return answerJson(response, 404, { code: "not_found", message: completed.message });
7325
+ return answerJson(response, 400, { code: "invalid_argument", message: completed.message });
7326
+ }
7327
+ if (completed.kind === "pass") body = completed.body;
7328
+ else {
7329
+ headers["content-length"] = String(completed.body.length);
7330
+ body = { chunks: [completed.body] };
7331
+ }
7332
+ }
7333
+ forward(request, response, target, headers, {
7334
+ body,
7335
+ timeouts: this.timeouts,
7336
+ fail: (reason2) => this.log(`local proxy: Work ${workId} api upstream failed: ${reason2}`)
7337
+ });
7338
+ }
7226
7339
  // A Storage content link the MCP route rewrote into a local handle: only a
7227
7340
  // handle issued to this Work reaches the platform's content endpoint, with
7228
7341
  // the query it stands for and the Work's identity that endpoint also asks
@@ -7358,7 +7471,7 @@ function runOnce(command) {
7358
7471
 
7359
7472
  // src/service/launchd.ts
7360
7473
  import { mkdir as mkdir10, rm as rm8, writeFile as writeFile3 } from "node:fs/promises";
7361
- import { join as join11 } from "node:path";
7474
+ import { join as join12 } from "node:path";
7362
7475
 
7363
7476
  // src/service/manager.ts
7364
7477
  import { execFile as execFile4 } from "node:child_process";
@@ -7401,13 +7514,13 @@ var LaunchAgentService = class {
7401
7514
  constructor(run2, home, uid, logFile) {
7402
7515
  this.run = run2;
7403
7516
  this.logFile = logFile;
7404
- this.plistFile = join11(home, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
7517
+ this.plistFile = join12(home, "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
7405
7518
  this.domain = `gui/${uid}`;
7406
7519
  this.target = `${this.domain}/${LAUNCHD_LABEL}`;
7407
7520
  }
7408
7521
  async install(spec) {
7409
- await mkdir10(join11(this.plistFile, ".."), { recursive: true });
7410
- await mkdir10(join11(spec.logFile, ".."), { recursive: true });
7522
+ await mkdir10(join12(this.plistFile, ".."), { recursive: true });
7523
+ await mkdir10(join12(spec.logFile, ".."), { recursive: true });
7411
7524
  await writeFile3(this.plistFile, launchAgentPlist(spec), { mode: 420 });
7412
7525
  await this.run("launchctl", ["bootout", this.target]);
7413
7526
  await mustRun(this.run, "launchctl", ["bootstrap", this.domain, this.plistFile]);
@@ -7489,7 +7602,7 @@ function xml(value) {
7489
7602
 
7490
7603
  // src/service/systemd.ts
7491
7604
  import { mkdir as mkdir11, rm as rm9, writeFile as writeFile4 } from "node:fs/promises";
7492
- import { join as join12 } from "node:path";
7605
+ import { join as join13 } from "node:path";
7493
7606
  var UNIT = `${SERVICE_NAME}.service`;
7494
7607
  var SystemdUserService = class {
7495
7608
  startsAt = 'at login. To start it at boot and keep it running after you log out, run "loginctl enable-linger".';
@@ -7497,10 +7610,10 @@ var SystemdUserService = class {
7497
7610
  unitFile;
7498
7611
  constructor(run2, home) {
7499
7612
  this.run = run2;
7500
- this.unitFile = join12(home, ".config", "systemd", "user", UNIT);
7613
+ this.unitFile = join13(home, ".config", "systemd", "user", UNIT);
7501
7614
  }
7502
7615
  async install(spec) {
7503
- await mkdir11(join12(this.unitFile, ".."), { recursive: true });
7616
+ await mkdir11(join13(this.unitFile, ".."), { recursive: true });
7504
7617
  await writeFile4(this.unitFile, systemdUnit(spec), { mode: 420 });
7505
7618
  await this.systemctl("daemon-reload");
7506
7619
  await this.systemctl("enable", UNIT);
@@ -7566,7 +7679,7 @@ function quote(value) {
7566
7679
 
7567
7680
  // src/service/task-scheduler.ts
7568
7681
  import { mkdir as mkdir12, rm as rm10, writeFile as writeFile5 } from "node:fs/promises";
7569
- import { join as join13 } from "node:path";
7682
+ import { join as join14 } from "node:path";
7570
7683
  var TASK_NAME = "Talgent Computer";
7571
7684
  var WRAPPER_FILE = "talgent-computer-service.cmd";
7572
7685
  var TASK_FILE = "talgent-computer-task.xml";
@@ -7586,9 +7699,9 @@ var LogonTaskService = class {
7586
7699
  }
7587
7700
  async install(spec) {
7588
7701
  await mkdir12(this.serviceDir, { recursive: true });
7589
- await mkdir12(join13(spec.logFile, ".."), { recursive: true });
7590
- const wrapper = join13(this.serviceDir, WRAPPER_FILE);
7591
- const definition = join13(this.serviceDir, TASK_FILE);
7702
+ await mkdir12(join14(spec.logFile, ".."), { recursive: true });
7703
+ const wrapper = join14(this.serviceDir, WRAPPER_FILE);
7704
+ const definition = join14(this.serviceDir, TASK_FILE);
7592
7705
  await writeFile5(wrapper, wrapperScript(spec));
7593
7706
  await writeFile5(definition, Buffer.concat([Buffer.from([255, 254]), Buffer.from(logonTask(spec, this.user, wrapper), "utf16le")]));
7594
7707
  await this.end();
@@ -7604,7 +7717,7 @@ var LogonTaskService = class {
7604
7717
  installed,
7605
7718
  running: installed && state === "Running",
7606
7719
  lastExit: installed && lastResult !== void 0 && /^\d+$/.test(lastResult) && state !== "Running" ? Number(lastResult) : void 0,
7607
- supervisesUpgrades: installed && await definesSupervised(join13(this.serviceDir, WRAPPER_FILE)),
7720
+ supervisesUpgrades: installed && await definesSupervised(join14(this.serviceDir, WRAPPER_FILE)),
7608
7721
  definition: `Task Scheduler: ${TASK_NAME}`,
7609
7722
  logs: this.logFile
7610
7723
  };
@@ -7615,8 +7728,8 @@ var LogonTaskService = class {
7615
7728
  await this.end();
7616
7729
  await mustRun(this.run, "schtasks", ["/Delete", "/TN", TASK_NAME, "/F"]);
7617
7730
  }
7618
- await rm10(join13(this.serviceDir, WRAPPER_FILE), { force: true });
7619
- await rm10(join13(this.serviceDir, TASK_FILE), { force: true });
7731
+ await rm10(join14(this.serviceDir, WRAPPER_FILE), { force: true });
7732
+ await rm10(join14(this.serviceDir, TASK_FILE), { force: true });
7620
7733
  return installed;
7621
7734
  }
7622
7735
  // Ending the task ends its console host only: the batch file and the run
@@ -8314,7 +8427,7 @@ function message(error) {
8314
8427
  // package.json
8315
8428
  var package_default = {
8316
8429
  name: "@talgent/computer",
8317
- version: "0.1.2",
8430
+ version: "0.2.0",
8318
8431
  description: "Talgent Computer: registers a machine as a Managed Computer and keeps it connected.",
8319
8432
  license: "Apache-2.0",
8320
8433
  repository: {
@@ -8373,7 +8486,7 @@ async function runMachine(io, credential, url) {
8373
8486
  restarting = true;
8374
8487
  controller.abort();
8375
8488
  });
8376
- const copiesDir = join14(dataDir, "claude-code");
8489
+ const copiesDir = join15(dataDir, "claude-code");
8377
8490
  const claudeCode = await prepareClaudeCode({ copiesDir }).catch((error) => {
8378
8491
  if (error instanceof ClaudeCodeMissingError) return error;
8379
8492
  throw error;
@@ -8416,10 +8529,10 @@ async function runMachine(io, credential, url) {
8416
8529
  async function connect2(options) {
8417
8530
  const { io, log, url, credential, claudeCode, processes, fence, upgrade } = options;
8418
8531
  const { dataDir } = io.paths;
8419
- const works = new Works({ worksDir: join14(dataDir, "works"), recordsDir: join14(dataDir, "state", "works") });
8420
- const credentials = new CredentialStore({ dir: join14(dataDir, "state", "credentials") });
8532
+ const works = new Works({ worksDir: join15(dataDir, "works"), recordsDir: join15(dataDir, "state", "works") });
8533
+ const credentials = new CredentialStore({ dir: join15(dataDir, "state", "credentials") });
8421
8534
  const proxy = new LocalProxy({
8422
- portFile: join14(dataDir, "state", "proxy.json"),
8535
+ portFile: join15(dataDir, "state", "proxy.json"),
8423
8536
  access: {
8424
8537
  tokenValid: (workId, token) => processes.proxyTokenValid(workId, token),
8425
8538
  credentials: (workId) => credentials.get(workId),
@@ -8435,7 +8548,7 @@ async function connect2(options) {
8435
8548
  processes,
8436
8549
  claudeBinDir: claudeCode.binDir,
8437
8550
  credentials,
8438
- inputs: new WorkInputs({ recordsDir: join14(dataDir, "state", "inputs"), credentials, log }),
8551
+ inputs: new WorkInputs({ recordsDir: join15(dataDir, "state", "inputs"), credentials, log }),
8439
8552
  proxyOrigin: () => proxy.origin(),
8440
8553
  baseEnv: process.env,
8441
8554
  home: homedir2(),
@@ -8502,22 +8615,22 @@ function selfUpgrade(dataDir, log, restart) {
8502
8615
  version: COMPUTER_VERSION,
8503
8616
  recordFile: upgradeRecordFile(dataDir),
8504
8617
  installer: "unavailable" in installation ? installation : new NpmInstaller(installation),
8505
- lock: new InstallLock({ file: join14(dataDir, "state", "upgrade.lock"), staleAfterMs: INSTALL_LOCK_MS, waitMs: INSTALL_LOCK_MS }),
8618
+ lock: new InstallLock({ file: join15(dataDir, "state", "upgrade.lock"), staleAfterMs: INSTALL_LOCK_MS, waitMs: INSTALL_LOCK_MS }),
8506
8619
  restart,
8507
8620
  log
8508
8621
  });
8509
8622
  }
8510
8623
  function runRecordFile(dataDir) {
8511
- return join14(dataDir, ...RUN_RECORD_PATH);
8624
+ return join15(dataDir, ...RUN_RECORD_PATH);
8512
8625
  }
8513
8626
  function upgradeRecordFile(dataDir) {
8514
- return join14(dataDir, "state", "upgrade.json");
8627
+ return join15(dataDir, "state", "upgrade.json");
8515
8628
  }
8516
8629
  function processTable(dataDir, uses) {
8517
- return new ProcessTable({ stateRoot: join14(dataDir, "state", "processes"), shim: [process.execPath, thisProgram(), SHIM_ARG], uses });
8630
+ return new ProcessTable({ stateRoot: join15(dataDir, "state", "processes"), shim: [process.execPath, thisProgram(), SHIM_ARG], uses });
8518
8631
  }
8519
8632
  function newFence(dataDir, processes, log) {
8520
- return new Fence({ stateFile: join14(dataDir, "state", "fence.json"), processes, log });
8633
+ return new Fence({ stateFile: join15(dataDir, "state", "fence.json"), processes, log });
8521
8634
  }
8522
8635
  async function restartWhenAsked(code, io) {
8523
8636
  if (code !== EXIT_RESTART || supervised(process.env)) return code;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@talgent/computer",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Talgent Computer: registers a machine as a Managed Computer and keeps it connected.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {