@poncho-ai/harness 0.28.1 → 0.28.3
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/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +22 -0
- package/dist/index.js +87 -9
- package/package.json +1 -1
- package/src/harness.ts +95 -8
- package/src/skill-tools.ts +6 -3
package/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @poncho-ai/harness@0.28.
|
|
2
|
+
> @poncho-ai/harness@0.28.3 build /home/runner/work/poncho-ai/poncho-ai/packages/harness
|
|
3
3
|
> node scripts/embed-docs.js && tsup src/index.ts --format esm --dts
|
|
4
4
|
|
|
5
5
|
[embed-docs] Generated poncho-docs.ts with 4 topics
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
[34mCLI[39m tsup v8.5.1
|
|
9
9
|
[34mCLI[39m Target: es2022
|
|
10
10
|
[34mESM[39m Build start
|
|
11
|
-
[32mESM[39m [1mdist/index.js [22m[
|
|
12
|
-
[32mESM[39m ⚡️ Build success in
|
|
11
|
+
[32mESM[39m [1mdist/index.js [22m[32m291.95 KB[39m
|
|
12
|
+
[32mESM[39m ⚡️ Build success in 123ms
|
|
13
13
|
[34mDTS[39m Build start
|
|
14
|
-
[32mDTS[39m ⚡️ Build success in
|
|
14
|
+
[32mDTS[39m ⚡️ Build success in 6599ms
|
|
15
15
|
[32mDTS[39m [1mdist/index.d.ts [22m[32m29.62 KB[39m
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# @poncho-ai/harness
|
|
2
2
|
|
|
3
|
+
## 0.28.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`87f844b`](https://github.com/cesr/poncho-ai/commit/87f844b0a76ece87e4bba78eaf73392f857cdef2) Thanks [@cesr](https://github.com/cesr)! - Fix tool execution blowing past serverless timeout and cross-skill script paths
|
|
8
|
+
- Race tool batch execution against remaining soft deadline so parallel tools can't push past the hard platform timeout
|
|
9
|
+
- Add post-tool-execution soft deadline checkpoint for tools that finish just past the deadline
|
|
10
|
+
- Allow skill scripts to reference sibling directories (e.g. ../scripts/current-date.ts)
|
|
11
|
+
- Catch script path normalization errors in approval check instead of crashing the run
|
|
12
|
+
|
|
13
|
+
## 0.28.2
|
|
14
|
+
|
|
15
|
+
### Patch Changes
|
|
16
|
+
|
|
17
|
+
- [`98df42f`](https://github.com/cesr/poncho-ai/commit/98df42f79e0a376d0a864598557758bfa644039d) Thanks [@cesr](https://github.com/cesr)! - Fix serverless subagent and continuation reliability
|
|
18
|
+
- Use stable internal secret across serverless instances for callback auth
|
|
19
|
+
- Wrap continuation self-fetches in waitUntil to survive function shutdown
|
|
20
|
+
- Set runStatus during callback re-runs so clients detect active processing
|
|
21
|
+
- Add post-streaming soft deadline check to catch long model responses
|
|
22
|
+
- Client auto-recovers from abrupt stream termination and orphaned continuations
|
|
23
|
+
- Fix callback continuation losing \_continuationMessages when no pending results
|
|
24
|
+
|
|
3
25
|
## 0.28.1
|
|
4
26
|
|
|
5
27
|
### Patch Changes
|
package/dist/index.js
CHANGED
|
@@ -1604,6 +1604,8 @@ Remote storage keys are namespaced and versioned, for example \`poncho:v1:<agent
|
|
|
1604
1604
|
| \`ANTHROPIC_API_KEY\` | Yes* | Claude API key |
|
|
1605
1605
|
| \`OPENAI_API_KEY\` | No | OpenAI API key (if using OpenAI) |
|
|
1606
1606
|
| \`PONCHO_AUTH_TOKEN\` | No | Unified auth token (Web UI passphrase + API Bearer token) |
|
|
1607
|
+
| \`PONCHO_INTERNAL_SECRET\` | No | Shared secret used by internal serverless callbacks (recommended for Vercel/Lambda) |
|
|
1608
|
+
| \`PONCHO_SELF_BASE_URL\` | No | Explicit base URL for internal self-callbacks when auto-detection is unavailable |
|
|
1607
1609
|
| \`OTEL_EXPORTER_OTLP_ENDPOINT\` | No | Telemetry destination |
|
|
1608
1610
|
| \`LATITUDE_API_KEY\` | No | Latitude dashboard integration |
|
|
1609
1611
|
| \`LATITUDE_PROJECT_ID\` | No | Latitude project identifier for capture traces |
|
|
@@ -4085,7 +4087,8 @@ var createSkillTools = (skills, options) => {
|
|
|
4085
4087
|
error: `Unknown skill: "${name}". Available skills: ${knownNames}`
|
|
4086
4088
|
};
|
|
4087
4089
|
}
|
|
4088
|
-
const
|
|
4090
|
+
const projectRoot = options?.workingDir ?? process.cwd();
|
|
4091
|
+
const resolved2 = resolveScriptPath(skill.skillDir, script, projectRoot);
|
|
4089
4092
|
if (options?.isScriptAllowed && !options.isScriptAllowed(name, resolved2.relativePath)) {
|
|
4090
4093
|
return {
|
|
4091
4094
|
error: `Script "${resolved2.relativePath}" for skill "${name}" is not allowed by policy.`
|
|
@@ -4173,7 +4176,7 @@ var collectScriptFiles = async (directory) => {
|
|
|
4173
4176
|
var normalizeScriptPolicyPath = (relativePath) => {
|
|
4174
4177
|
const trimmed = relativePath.trim();
|
|
4175
4178
|
const normalized = normalize2(trimmed).split(sep2).join("/");
|
|
4176
|
-
if (normalized.startsWith("
|
|
4179
|
+
if (normalized.startsWith("/")) {
|
|
4177
4180
|
throw new Error("Script path must be relative and within the allowed directory");
|
|
4178
4181
|
}
|
|
4179
4182
|
const withoutDotPrefix = normalized.startsWith("./") ? normalized.slice(2) : normalized;
|
|
@@ -4182,10 +4185,11 @@ var normalizeScriptPolicyPath = (relativePath) => {
|
|
|
4182
4185
|
}
|
|
4183
4186
|
return withoutDotPrefix;
|
|
4184
4187
|
};
|
|
4185
|
-
var resolveScriptPath = (baseDir, relativePath) => {
|
|
4188
|
+
var resolveScriptPath = (baseDir, relativePath, containmentDir) => {
|
|
4186
4189
|
const normalized = normalizeScriptPolicyPath(relativePath);
|
|
4187
4190
|
const fullPath = resolve9(baseDir, normalized);
|
|
4188
|
-
|
|
4191
|
+
const boundary = resolve9(containmentDir ?? baseDir);
|
|
4192
|
+
if (!fullPath.startsWith(`${boundary}${sep2}`) && fullPath !== boundary) {
|
|
4189
4193
|
throw new Error("Script path must stay inside the allowed directory");
|
|
4190
4194
|
}
|
|
4191
4195
|
const extension = extname(fullPath).toLowerCase();
|
|
@@ -5323,10 +5327,15 @@ var AgentHarness = class _AgentHarness {
|
|
|
5323
5327
|
if (!rawScript) {
|
|
5324
5328
|
return false;
|
|
5325
5329
|
}
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
+
let canonicalPath;
|
|
5331
|
+
try {
|
|
5332
|
+
canonicalPath = normalizeRelativeScriptPattern(
|
|
5333
|
+
`./${normalizeScriptPolicyPath(rawScript)}`,
|
|
5334
|
+
"run_skill_script input.script"
|
|
5335
|
+
);
|
|
5336
|
+
} catch {
|
|
5337
|
+
return true;
|
|
5338
|
+
}
|
|
5330
5339
|
const scriptPatterns = this.getRequestedScriptApprovalPatterns();
|
|
5331
5340
|
return scriptPatterns.some(
|
|
5332
5341
|
(pattern) => matchesRelativeScriptPattern(canonicalPath, pattern)
|
|
@@ -6347,6 +6356,22 @@ ${textContent}` };
|
|
|
6347
6356
|
yield emitCancellation();
|
|
6348
6357
|
return;
|
|
6349
6358
|
}
|
|
6359
|
+
if (softDeadlineMs > 0 && now() - start > softDeadlineMs) {
|
|
6360
|
+
const result_ = {
|
|
6361
|
+
status: "completed",
|
|
6362
|
+
response: responseText + fullText,
|
|
6363
|
+
steps: step,
|
|
6364
|
+
tokens: { input: totalInputTokens, output: totalOutputTokens, cached: totalCachedTokens },
|
|
6365
|
+
duration: now() - start,
|
|
6366
|
+
continuation: true,
|
|
6367
|
+
continuationMessages: [...messages],
|
|
6368
|
+
maxSteps,
|
|
6369
|
+
contextTokens: latestContextTokens + toolOutputEstimateSinceModel,
|
|
6370
|
+
contextWindow
|
|
6371
|
+
};
|
|
6372
|
+
yield pushEvent({ type: "run:completed", runId, result: result_ });
|
|
6373
|
+
return;
|
|
6374
|
+
}
|
|
6350
6375
|
const finishReason = await result.finishReason;
|
|
6351
6376
|
if (finishReason === "error") {
|
|
6352
6377
|
yield pushEvent({
|
|
@@ -6531,7 +6556,44 @@ ${textContent}` };
|
|
|
6531
6556
|
);
|
|
6532
6557
|
}
|
|
6533
6558
|
}
|
|
6534
|
-
const
|
|
6559
|
+
const TOOL_DEADLINE_SENTINEL = /* @__PURE__ */ Symbol("tool_deadline");
|
|
6560
|
+
const toolDeadlineRemainingMs = softDeadlineMs > 0 ? softDeadlineMs - (now() - start) : Infinity;
|
|
6561
|
+
let batchResults;
|
|
6562
|
+
if (approvedCalls.length === 0) {
|
|
6563
|
+
batchResults = [];
|
|
6564
|
+
} else if (toolDeadlineRemainingMs <= 0) {
|
|
6565
|
+
batchResults = TOOL_DEADLINE_SENTINEL;
|
|
6566
|
+
} else if (toolDeadlineRemainingMs < Infinity) {
|
|
6567
|
+
const raced = await Promise.race([
|
|
6568
|
+
this.dispatcher.executeBatch(approvedCalls, toolContext),
|
|
6569
|
+
new Promise(
|
|
6570
|
+
(resolve12) => setTimeout(() => resolve12(TOOL_DEADLINE_SENTINEL), toolDeadlineRemainingMs)
|
|
6571
|
+
)
|
|
6572
|
+
]);
|
|
6573
|
+
if (raced === TOOL_DEADLINE_SENTINEL) {
|
|
6574
|
+
batchResults = TOOL_DEADLINE_SENTINEL;
|
|
6575
|
+
} else {
|
|
6576
|
+
batchResults = raced;
|
|
6577
|
+
}
|
|
6578
|
+
} else {
|
|
6579
|
+
batchResults = await this.dispatcher.executeBatch(approvedCalls, toolContext);
|
|
6580
|
+
}
|
|
6581
|
+
if (batchResults === TOOL_DEADLINE_SENTINEL) {
|
|
6582
|
+
const result_ = {
|
|
6583
|
+
status: "completed",
|
|
6584
|
+
response: responseText + fullText,
|
|
6585
|
+
steps: step,
|
|
6586
|
+
tokens: { input: totalInputTokens, output: totalOutputTokens, cached: totalCachedTokens },
|
|
6587
|
+
duration: now() - start,
|
|
6588
|
+
continuation: true,
|
|
6589
|
+
continuationMessages: [...messages],
|
|
6590
|
+
maxSteps,
|
|
6591
|
+
contextTokens: latestContextTokens + toolOutputEstimateSinceModel,
|
|
6592
|
+
contextWindow
|
|
6593
|
+
};
|
|
6594
|
+
yield pushEvent({ type: "run:completed", runId, result: result_ });
|
|
6595
|
+
return;
|
|
6596
|
+
}
|
|
6535
6597
|
if (isCancelled()) {
|
|
6536
6598
|
yield emitCancellation();
|
|
6537
6599
|
return;
|
|
@@ -6619,6 +6681,22 @@ ${textContent}` };
|
|
|
6619
6681
|
content: JSON.stringify(toolResultsForModel),
|
|
6620
6682
|
metadata: toolMsgMeta
|
|
6621
6683
|
});
|
|
6684
|
+
if (softDeadlineMs > 0 && now() - start > softDeadlineMs) {
|
|
6685
|
+
const result_ = {
|
|
6686
|
+
status: "completed",
|
|
6687
|
+
response: responseText + fullText,
|
|
6688
|
+
steps: step,
|
|
6689
|
+
tokens: { input: totalInputTokens, output: totalOutputTokens, cached: totalCachedTokens },
|
|
6690
|
+
duration: now() - start,
|
|
6691
|
+
continuation: true,
|
|
6692
|
+
continuationMessages: [...messages],
|
|
6693
|
+
maxSteps,
|
|
6694
|
+
contextTokens: latestContextTokens + toolOutputEstimateSinceModel,
|
|
6695
|
+
contextWindow
|
|
6696
|
+
};
|
|
6697
|
+
yield pushEvent({ type: "run:completed", runId, result: result_ });
|
|
6698
|
+
return;
|
|
6699
|
+
}
|
|
6622
6700
|
if (this.environment === "development") {
|
|
6623
6701
|
const agentChanged = await this.refreshAgentIfChanged();
|
|
6624
6702
|
const skillsChanged = await this.refreshSkillsIfChanged(true);
|
package/package.json
CHANGED
package/src/harness.ts
CHANGED
|
@@ -812,10 +812,15 @@ export class AgentHarness {
|
|
|
812
812
|
if (!rawScript) {
|
|
813
813
|
return false;
|
|
814
814
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
815
|
+
let canonicalPath: string;
|
|
816
|
+
try {
|
|
817
|
+
canonicalPath = normalizeRelativeScriptPattern(
|
|
818
|
+
`./${normalizeScriptPolicyPath(rawScript)}`,
|
|
819
|
+
"run_skill_script input.script",
|
|
820
|
+
);
|
|
821
|
+
} catch {
|
|
822
|
+
return true;
|
|
823
|
+
}
|
|
819
824
|
const scriptPatterns = this.getRequestedScriptApprovalPatterns();
|
|
820
825
|
return scriptPatterns.some((pattern) =>
|
|
821
826
|
matchesRelativeScriptPattern(canonicalPath, pattern),
|
|
@@ -2030,6 +2035,25 @@ ${boundedMainMemory.trim()}`
|
|
|
2030
2035
|
return;
|
|
2031
2036
|
}
|
|
2032
2037
|
|
|
2038
|
+
// Post-streaming soft deadline: if the model stream took long enough to
|
|
2039
|
+
// push past the soft deadline, checkpoint now before tool execution.
|
|
2040
|
+
if (softDeadlineMs > 0 && now() - start > softDeadlineMs) {
|
|
2041
|
+
const result_: RunResult = {
|
|
2042
|
+
status: "completed",
|
|
2043
|
+
response: responseText + fullText,
|
|
2044
|
+
steps: step,
|
|
2045
|
+
tokens: { input: totalInputTokens, output: totalOutputTokens, cached: totalCachedTokens },
|
|
2046
|
+
duration: now() - start,
|
|
2047
|
+
continuation: true,
|
|
2048
|
+
continuationMessages: [...messages],
|
|
2049
|
+
maxSteps,
|
|
2050
|
+
contextTokens: latestContextTokens + toolOutputEstimateSinceModel,
|
|
2051
|
+
contextWindow,
|
|
2052
|
+
};
|
|
2053
|
+
yield pushEvent({ type: "run:completed", runId, result: result_ });
|
|
2054
|
+
return;
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2033
2057
|
// Check finish reason for error / abnormal completions.
|
|
2034
2058
|
const finishReason = await result.finishReason;
|
|
2035
2059
|
|
|
@@ -2266,10 +2290,53 @@ ${boundedMainMemory.trim()}`
|
|
|
2266
2290
|
}
|
|
2267
2291
|
}
|
|
2268
2292
|
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2293
|
+
// Race tool execution against the soft deadline so long-running tool
|
|
2294
|
+
// batches (e.g. 4 parallel web_search calls) can't push us past the
|
|
2295
|
+
// hard platform timeout. If the deadline fires first, we checkpoint
|
|
2296
|
+
// with the pre-tool messages and the step will be re-done on
|
|
2297
|
+
// continuation (assistant + tool results are not yet in `messages`).
|
|
2298
|
+
const TOOL_DEADLINE_SENTINEL = Symbol("tool_deadline");
|
|
2299
|
+
const toolDeadlineRemainingMs = softDeadlineMs > 0
|
|
2300
|
+
? softDeadlineMs - (now() - start)
|
|
2301
|
+
: Infinity;
|
|
2302
|
+
|
|
2303
|
+
let batchResults: Awaited<ReturnType<typeof this.dispatcher.executeBatch>>;
|
|
2304
|
+
if (approvedCalls.length === 0) {
|
|
2305
|
+
batchResults = [];
|
|
2306
|
+
} else if (toolDeadlineRemainingMs <= 0) {
|
|
2307
|
+
batchResults = TOOL_DEADLINE_SENTINEL as never;
|
|
2308
|
+
} else if (toolDeadlineRemainingMs < Infinity) {
|
|
2309
|
+
const raced = await Promise.race([
|
|
2310
|
+
this.dispatcher.executeBatch(approvedCalls, toolContext),
|
|
2311
|
+
new Promise<typeof TOOL_DEADLINE_SENTINEL>((resolve) =>
|
|
2312
|
+
setTimeout(() => resolve(TOOL_DEADLINE_SENTINEL), toolDeadlineRemainingMs),
|
|
2313
|
+
),
|
|
2314
|
+
]);
|
|
2315
|
+
if (raced === TOOL_DEADLINE_SENTINEL) {
|
|
2316
|
+
batchResults = TOOL_DEADLINE_SENTINEL as never;
|
|
2317
|
+
} else {
|
|
2318
|
+
batchResults = raced;
|
|
2319
|
+
}
|
|
2320
|
+
} else {
|
|
2321
|
+
batchResults = await this.dispatcher.executeBatch(approvedCalls, toolContext);
|
|
2322
|
+
}
|
|
2323
|
+
|
|
2324
|
+
if ((batchResults as unknown) === TOOL_DEADLINE_SENTINEL) {
|
|
2325
|
+
const result_: RunResult = {
|
|
2326
|
+
status: "completed",
|
|
2327
|
+
response: responseText + fullText,
|
|
2328
|
+
steps: step,
|
|
2329
|
+
tokens: { input: totalInputTokens, output: totalOutputTokens, cached: totalCachedTokens },
|
|
2330
|
+
duration: now() - start,
|
|
2331
|
+
continuation: true,
|
|
2332
|
+
continuationMessages: [...messages],
|
|
2333
|
+
maxSteps,
|
|
2334
|
+
contextTokens: latestContextTokens + toolOutputEstimateSinceModel,
|
|
2335
|
+
contextWindow,
|
|
2336
|
+
};
|
|
2337
|
+
yield pushEvent({ type: "run:completed", runId, result: result_ });
|
|
2338
|
+
return;
|
|
2339
|
+
}
|
|
2273
2340
|
|
|
2274
2341
|
if (isCancelled()) {
|
|
2275
2342
|
yield emitCancellation();
|
|
@@ -2367,6 +2434,26 @@ ${boundedMainMemory.trim()}`
|
|
|
2367
2434
|
metadata: toolMsgMeta as Message["metadata"],
|
|
2368
2435
|
});
|
|
2369
2436
|
|
|
2437
|
+
// Post-tool-execution soft deadline: long-running tool batches (e.g.
|
|
2438
|
+
// multiple web_search calls) can push past the deadline. Checkpoint
|
|
2439
|
+
// now so the platform doesn't hard-kill us before we can continue.
|
|
2440
|
+
if (softDeadlineMs > 0 && now() - start > softDeadlineMs) {
|
|
2441
|
+
const result_: RunResult = {
|
|
2442
|
+
status: "completed",
|
|
2443
|
+
response: responseText + fullText,
|
|
2444
|
+
steps: step,
|
|
2445
|
+
tokens: { input: totalInputTokens, output: totalOutputTokens, cached: totalCachedTokens },
|
|
2446
|
+
duration: now() - start,
|
|
2447
|
+
continuation: true,
|
|
2448
|
+
continuationMessages: [...messages],
|
|
2449
|
+
maxSteps,
|
|
2450
|
+
contextTokens: latestContextTokens + toolOutputEstimateSinceModel,
|
|
2451
|
+
contextWindow,
|
|
2452
|
+
};
|
|
2453
|
+
yield pushEvent({ type: "run:completed", runId, result: result_ });
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
|
|
2370
2457
|
// In development, re-read AGENT.md and re-scan skills after tool
|
|
2371
2458
|
// execution so changes are available on the next step without
|
|
2372
2459
|
// requiring a server restart.
|
package/src/skill-tools.ts
CHANGED
|
@@ -244,7 +244,8 @@ export const createSkillTools = (
|
|
|
244
244
|
error: `Unknown skill: "${name}". Available skills: ${knownNames}`,
|
|
245
245
|
};
|
|
246
246
|
}
|
|
247
|
-
const
|
|
247
|
+
const projectRoot = options?.workingDir ?? process.cwd();
|
|
248
|
+
const resolved = resolveScriptPath(skill.skillDir, script, projectRoot);
|
|
248
249
|
if (
|
|
249
250
|
options?.isScriptAllowed &&
|
|
250
251
|
!options.isScriptAllowed(name, resolved.relativePath)
|
|
@@ -357,7 +358,7 @@ const collectScriptFiles = async (directory: string): Promise<string[]> => {
|
|
|
357
358
|
export const normalizeScriptPolicyPath = (relativePath: string): string => {
|
|
358
359
|
const trimmed = relativePath.trim();
|
|
359
360
|
const normalized = normalize(trimmed).split(sep).join("/");
|
|
360
|
-
if (normalized.startsWith("
|
|
361
|
+
if (normalized.startsWith("/")) {
|
|
361
362
|
throw new Error("Script path must be relative and within the allowed directory");
|
|
362
363
|
}
|
|
363
364
|
const withoutDotPrefix = normalized.startsWith("./") ? normalized.slice(2) : normalized;
|
|
@@ -370,10 +371,12 @@ export const normalizeScriptPolicyPath = (relativePath: string): string => {
|
|
|
370
371
|
const resolveScriptPath = (
|
|
371
372
|
baseDir: string,
|
|
372
373
|
relativePath: string,
|
|
374
|
+
containmentDir?: string,
|
|
373
375
|
): { fullPath: string; relativePath: string } => {
|
|
374
376
|
const normalized = normalizeScriptPolicyPath(relativePath);
|
|
375
377
|
const fullPath = resolve(baseDir, normalized);
|
|
376
|
-
|
|
378
|
+
const boundary = resolve(containmentDir ?? baseDir);
|
|
379
|
+
if (!fullPath.startsWith(`${boundary}${sep}`) && fullPath !== boundary) {
|
|
377
380
|
throw new Error("Script path must stay inside the allowed directory");
|
|
378
381
|
}
|
|
379
382
|
const extension = extname(fullPath).toLowerCase();
|