@arcadeai/hypotenuse 0.16.1 → 0.17.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/hypotenuse.js +65 -9
- package/package.json +1 -1
package/dist/hypotenuse.js
CHANGED
|
@@ -343,7 +343,7 @@ import { Command } from "commander";
|
|
|
343
343
|
// package.json
|
|
344
344
|
var package_default = {
|
|
345
345
|
name: "@arcadeai/hypotenuse",
|
|
346
|
-
version: "0.
|
|
346
|
+
version: "0.17.0",
|
|
347
347
|
description: "Hypotenuse \u2014 run coding agents on your machine for Linear agent sessions",
|
|
348
348
|
type: "module",
|
|
349
349
|
license: "Apache-2.0",
|
|
@@ -2137,6 +2137,9 @@ function expandPath(raw, baseDir, env = process.env) {
|
|
|
2137
2137
|
value = value.replace(/\$(?:\{([A-Za-z_][A-Za-z0-9_]*)\}|([A-Za-z_][A-Za-z0-9_]*))/g, (_, braced, bare) => env[braced ?? bare] ?? "");
|
|
2138
2138
|
return isAbsolute(value) ? resolve(value) : resolve(baseDir, value);
|
|
2139
2139
|
}
|
|
2140
|
+
function isRemoteSource(source) {
|
|
2141
|
+
return /^https?:\/\//i.test(source.trim());
|
|
2142
|
+
}
|
|
2140
2143
|
function tildify(path, env = process.env) {
|
|
2141
2144
|
const home = homeOf(env);
|
|
2142
2145
|
return path === home ? "~" : path.startsWith(`${home}/`) ? `~/${path.slice(home.length + 1)}` : path;
|
|
@@ -2201,6 +2204,9 @@ function machineContext(baseDir, env = process.env) {
|
|
|
2201
2204
|
}
|
|
2202
2205
|
function buildConfigSchema(ctx) {
|
|
2203
2206
|
const path = () => z9.string().trim().min(1).transform((value) => expandPath(value, ctx.baseDir, ctx.env));
|
|
2207
|
+
const source = () => z9.string().trim().min(1).refine((value) => !isRemoteSource(value) || URL.canParse(value), {
|
|
2208
|
+
message: "expected a path or an http(s) URL"
|
|
2209
|
+
}).transform((value) => isRemoteSource(value) ? value : expandPath(value, ctx.baseDir, ctx.env));
|
|
2204
2210
|
const duration = () => z9.string().regex(DURATION_PATTERN, "expected a duration such as 45s, 10m, 2h, or 7d").transform(parseDuration);
|
|
2205
2211
|
const size = () => z9.string().regex(SIZE_PATTERN, "expected a size such as 500MB or 2GB").transform(parseSize);
|
|
2206
2212
|
const parallel = () => z9.number().int().min(1).max(32);
|
|
@@ -2223,7 +2229,8 @@ function buildConfigSchema(ctx) {
|
|
|
2223
2229
|
const role = (fallbackPolicy) => z9.strictObject({
|
|
2224
2230
|
agent: z9.string().trim().min(1).describe("An [agents.<id>] id or a preset name"),
|
|
2225
2231
|
prompt: z9.string().min(1).optional().describe("The role's entry prompt template, inline"),
|
|
2226
|
-
prompt_file:
|
|
2232
|
+
prompt_file: source().optional().describe("A Markdown prompt template file, or a URL of instructions the agent reads with its own tools"),
|
|
2233
|
+
docs: z9.array(source()).default([]).describe("Documents added to the role's instructions: a file's text is included, a URL is linked for the agent to read with its own tools"),
|
|
2227
2234
|
permission_policy: permissionPolicy(fallbackPolicy),
|
|
2228
2235
|
max_parallel: parallel().optional().describe("Jobs of this role at once"),
|
|
2229
2236
|
mode: z9.string().trim().min(1).optional().describe("The ACP session mode to select"),
|
|
@@ -3315,7 +3322,7 @@ function validateMachine(config, machine, options = {}) {
|
|
|
3315
3322
|
]) {
|
|
3316
3323
|
if (!table)
|
|
3317
3324
|
continue;
|
|
3318
|
-
if (table.prompt_file !== undefined) {
|
|
3325
|
+
if (table.prompt_file !== undefined && !isRemoteSource(table.prompt_file)) {
|
|
3319
3326
|
const text = machine.readFile(table.prompt_file);
|
|
3320
3327
|
if (text === null) {
|
|
3321
3328
|
errors.push({
|
|
@@ -3326,6 +3333,14 @@ function validateMachine(config, machine, options = {}) {
|
|
|
3326
3333
|
checkTemplate(`roles.${role}.prompt_file`, text, errors);
|
|
3327
3334
|
}
|
|
3328
3335
|
}
|
|
3336
|
+
table.docs.forEach((doc, index) => {
|
|
3337
|
+
if (isRemoteSource(doc) || machine.readFile(doc) !== null)
|
|
3338
|
+
return;
|
|
3339
|
+
errors.push({
|
|
3340
|
+
path: `roles.${role}.docs[${index}]`,
|
|
3341
|
+
message: `${home(doc)} cannot be read`
|
|
3342
|
+
});
|
|
3343
|
+
});
|
|
3329
3344
|
table.extra_mcp_servers.forEach((server, index) => {
|
|
3330
3345
|
const at = `roles.${role}.extra_mcp_servers[${index}]`;
|
|
3331
3346
|
for (const [key, name] of [
|
|
@@ -6209,6 +6224,26 @@ function fenceFor(text) {
|
|
|
6209
6224
|
const longest = Math.max(0, ...[...text.matchAll(/`+/g)].map((m) => m[0].length));
|
|
6210
6225
|
return "`".repeat(Math.max(3, longest + 1));
|
|
6211
6226
|
}
|
|
6227
|
+
function remoteInstructions(url) {
|
|
6228
|
+
return `Your instructions for this role are the document at ${url}. Read it with your tools before doing anything else, and follow it. If you cannot read it, say so in your response and stop.`;
|
|
6229
|
+
}
|
|
6230
|
+
function roleDocsSection(docs) {
|
|
6231
|
+
if (docs.length === 0)
|
|
6232
|
+
return "";
|
|
6233
|
+
const links = docs.filter((d) => d.text === null).map((d) => d.source);
|
|
6234
|
+
const files = docs.filter((d) => d.text !== null);
|
|
6235
|
+
return [
|
|
6236
|
+
"These documents are part of your instructions: they say how this team works. Where one disagrees with the instructions above about your tools or what you never do, the instructions above win.",
|
|
6237
|
+
links.length ? `Read each of these with your tools before you begin. If one cannot be read, say so in your response.
|
|
6238
|
+
${links.map((l) => `- ${l}`).join(`
|
|
6239
|
+
`)}` : null,
|
|
6240
|
+
...files.map((d) => `<doc source=${attr(d.source)}>
|
|
6241
|
+
${(d.text ?? "").trim()}
|
|
6242
|
+
</doc>`)
|
|
6243
|
+
].filter(Boolean).join(`
|
|
6244
|
+
|
|
6245
|
+
`);
|
|
6246
|
+
}
|
|
6212
6247
|
function reviewSection(review) {
|
|
6213
6248
|
const issue = review.childIssueIdentifier ?? "the dispatched issue";
|
|
6214
6249
|
const body = [
|
|
@@ -6315,7 +6350,8 @@ ${g.body.trim()}
|
|
|
6315
6350
|
`);
|
|
6316
6351
|
const text = [
|
|
6317
6352
|
section("hypotenuse", `You are working as the ${assignment.role} agent for ${who}${creator ? ` on behalf of ${creator}` : ""}, in a Linear agent session. You are an AI agent; never claim to be a person. What you say at the end of your turn is posted to the session as your response.${dispatchedBy(assignment)}`),
|
|
6318
|
-
section("role-instructions", renderTemplate(template, values, onUnknown)),
|
|
6353
|
+
section("role-instructions", ctx.templateUrl ? remoteInstructions(ctx.templateUrl) : renderTemplate(template, values, onUnknown)),
|
|
6354
|
+
section("role-docs", roleDocsSection(ctx.docs ?? [])),
|
|
6319
6355
|
section("linear-context", assignment.promptContext),
|
|
6320
6356
|
section("guidance", guidance),
|
|
6321
6357
|
section("conversation", conversation(assignment)),
|
|
@@ -6908,11 +6944,29 @@ class Turn {
|
|
|
6908
6944
|
const conn = this.conn;
|
|
6909
6945
|
const sessionId = this.acpSessionId;
|
|
6910
6946
|
let templateText = null;
|
|
6911
|
-
|
|
6912
|
-
|
|
6913
|
-
|
|
6914
|
-
|
|
6915
|
-
|
|
6947
|
+
let templateUrl = null;
|
|
6948
|
+
const docs = [];
|
|
6949
|
+
if (!this.resumed) {
|
|
6950
|
+
const promptFile = this.role?.prompt_file;
|
|
6951
|
+
if (promptFile && isRemoteSource(promptFile))
|
|
6952
|
+
templateUrl = promptFile;
|
|
6953
|
+
else if (promptFile) {
|
|
6954
|
+
try {
|
|
6955
|
+
templateText = readFileSync7(promptFile, "utf8");
|
|
6956
|
+
} catch (error) {
|
|
6957
|
+
throw new TurnFailure(`Runner ${ctx.runnerName} cannot read its ${ctx.assignment.role} prompt file: ${errorMessage(error)}`);
|
|
6958
|
+
}
|
|
6959
|
+
}
|
|
6960
|
+
for (const doc of this.role?.docs ?? []) {
|
|
6961
|
+
if (isRemoteSource(doc)) {
|
|
6962
|
+
docs.push({ source: doc, text: null });
|
|
6963
|
+
continue;
|
|
6964
|
+
}
|
|
6965
|
+
try {
|
|
6966
|
+
docs.push({ source: tildify(doc), text: readFileSync7(doc, "utf8") });
|
|
6967
|
+
} catch (error) {
|
|
6968
|
+
throw new TurnFailure(`Runner ${ctx.runnerName} cannot read a ${ctx.assignment.role} doc: ${errorMessage(error)}`);
|
|
6969
|
+
}
|
|
6916
6970
|
}
|
|
6917
6971
|
}
|
|
6918
6972
|
const blocks = buildPrompt(ctx.assignment, this.role, {
|
|
@@ -6922,6 +6976,8 @@ class Turn {
|
|
|
6922
6976
|
runnerName: ctx.runnerName,
|
|
6923
6977
|
resumed: this.resumed,
|
|
6924
6978
|
templateText,
|
|
6979
|
+
templateUrl,
|
|
6980
|
+
docs,
|
|
6925
6981
|
readOnly: this.workspace?.mode === "readonly"
|
|
6926
6982
|
}, (name) => ctx.log.warn(`the prompt template uses an unknown variable {{${name}}}; it rendered empty`));
|
|
6927
6983
|
this.promptStartedAt = this.now();
|