@poncho-ai/harness 0.29.0 → 0.31.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/.turbo/turbo-build.log +5 -5
- package/CHANGELOG.md +24 -0
- package/dist/index.d.ts +45 -1
- package/dist/index.js +466 -57
- package/package.json +2 -2
- package/src/agent-parser.ts +1 -1
- package/src/config.ts +7 -0
- package/src/harness.ts +61 -6
- package/src/index.ts +1 -0
- package/src/model-factory.ts +87 -0
- package/src/openai-codex-auth.ts +362 -0
- package/src/state.ts +4 -0
- package/test/agent-parser.test.ts +12 -0
- package/test/model-factory.test.ts +12 -2
- package/test/openai-codex-auth.test.ts +82 -0
package/dist/index.js
CHANGED
|
@@ -134,7 +134,7 @@ var parseAgentMarkdown = (content) => {
|
|
|
134
134
|
if (typeof parsed.name !== "string" || parsed.name.trim() === "") {
|
|
135
135
|
throw new Error("Invalid AGENT.md: frontmatter requires a non-empty `name`.");
|
|
136
136
|
}
|
|
137
|
-
const KNOWN_PROVIDERS = /* @__PURE__ */ new Set(["anthropic", "openai"]);
|
|
137
|
+
const KNOWN_PROVIDERS = /* @__PURE__ */ new Set(["anthropic", "openai", "openai-codex"]);
|
|
138
138
|
const splitProviderPrefix = (raw) => {
|
|
139
139
|
const slashIdx = raw.indexOf("/");
|
|
140
140
|
if (slashIdx > 0) {
|
|
@@ -1440,6 +1440,11 @@ All credentials in \`poncho.config.js\` use **env var name** fields (\`*Env\` su
|
|
|
1440
1440
|
|---|---|---|
|
|
1441
1441
|
| \`providers.anthropic.apiKeyEnv\` | \`ANTHROPIC_API_KEY\` | Anthropic model API key |
|
|
1442
1442
|
| \`providers.openai.apiKeyEnv\` | \`OPENAI_API_KEY\` | OpenAI model API key |
|
|
1443
|
+
| \`providers.openaiCodex.refreshTokenEnv\` | \`OPENAI_CODEX_REFRESH_TOKEN\` | OpenAI Codex OAuth refresh token |
|
|
1444
|
+
| \`providers.openaiCodex.accountIdEnv\` | \`OPENAI_CODEX_ACCOUNT_ID\` | OpenAI Codex account/org routing header (optional) |
|
|
1445
|
+
| \`providers.openaiCodex.accessTokenEnv\` | \`OPENAI_CODEX_ACCESS_TOKEN\` | OpenAI Codex OAuth access token seed (optional) |
|
|
1446
|
+
| \`providers.openaiCodex.accessTokenExpiresAtEnv\` | \`OPENAI_CODEX_ACCESS_TOKEN_EXPIRES_AT\` | Access token epoch expiry in ms (optional) |
|
|
1447
|
+
| \`providers.openaiCodex.authFilePathEnv\` | \`OPENAI_CODEX_AUTH_FILE\` | Overrides local auth file path used by \`poncho auth\` |
|
|
1443
1448
|
| \`auth.tokenEnv\` | \`PONCHO_AUTH_TOKEN\` | Auth passphrase / bearer token |
|
|
1444
1449
|
| \`storage.urlEnv\` | \`UPSTASH_REDIS_REST_URL\` / \`REDIS_URL\` | Storage connection URL |
|
|
1445
1450
|
| \`storage.tokenEnv\` | \`UPSTASH_REDIS_REST_TOKEN\` | Upstash REST token |
|
|
@@ -1511,6 +1516,10 @@ export default {
|
|
|
1511
1516
|
providers: {
|
|
1512
1517
|
// anthropic: { apiKeyEnv: 'ANTHROPIC_API_KEY' }, // default
|
|
1513
1518
|
// openai: { apiKeyEnv: 'OPENAI_API_KEY' }, // default
|
|
1519
|
+
// openaiCodex: {
|
|
1520
|
+
// refreshTokenEnv: 'OPENAI_CODEX_REFRESH_TOKEN',
|
|
1521
|
+
// accountIdEnv: 'OPENAI_CODEX_ACCOUNT_ID', // optional
|
|
1522
|
+
// },
|
|
1514
1523
|
},
|
|
1515
1524
|
|
|
1516
1525
|
// Unified storage (preferred). Replaces separate \`state\` and \`memory\` blocks.
|
|
@@ -1609,6 +1618,11 @@ Remote storage keys are namespaced and versioned, for example \`poncho:v1:<agent
|
|
|
1609
1618
|
|----------|----------|-------------|
|
|
1610
1619
|
| \`ANTHROPIC_API_KEY\` | Yes* | Claude API key |
|
|
1611
1620
|
| \`OPENAI_API_KEY\` | No | OpenAI API key (if using OpenAI) |
|
|
1621
|
+
| \`OPENAI_CODEX_REFRESH_TOKEN\` | No | OpenAI Codex OAuth refresh token (if using \`model.provider: openai-codex\`) |
|
|
1622
|
+
| \`OPENAI_CODEX_ACCOUNT_ID\` | No | OpenAI Codex account/org id for request routing (optional) |
|
|
1623
|
+
| \`OPENAI_CODEX_ACCESS_TOKEN\` | No | Optional pre-seeded short-lived access token |
|
|
1624
|
+
| \`OPENAI_CODEX_ACCESS_TOKEN_EXPIRES_AT\` | No | Epoch millis expiry for \`OPENAI_CODEX_ACCESS_TOKEN\` |
|
|
1625
|
+
| \`OPENAI_CODEX_AUTH_FILE\` | No | Local auth store override for \`poncho auth\` commands |
|
|
1612
1626
|
| \`PONCHO_AUTH_TOKEN\` | No | Unified auth token (Web UI passphrase + API Bearer token) |
|
|
1613
1627
|
| \`PONCHO_INTERNAL_SECRET\` | No | Shared secret used by internal serverless callbacks (recommended for Vercel/Lambda) |
|
|
1614
1628
|
| \`PONCHO_SELF_BASE_URL\` | No | Explicit base URL for internal self-callbacks when auto-detection is unavailable |
|
|
@@ -1833,6 +1847,36 @@ Make sure you have a \`.env\` file with your API key:
|
|
|
1833
1847
|
echo "ANTHROPIC_API_KEY=sk-ant-..." > .env
|
|
1834
1848
|
\`\`\`
|
|
1835
1849
|
|
|
1850
|
+
### "OpenAI Codex credentials not found"
|
|
1851
|
+
|
|
1852
|
+
Bootstrap credentials with device auth, then export env values:
|
|
1853
|
+
|
|
1854
|
+
\`\`\`bash
|
|
1855
|
+
poncho auth login --provider openai-codex --device
|
|
1856
|
+
poncho auth export --provider openai-codex --format env
|
|
1857
|
+
\`\`\`
|
|
1858
|
+
|
|
1859
|
+
For production, copy exported values into your deployment secret manager (not committed files).
|
|
1860
|
+
|
|
1861
|
+
### "OpenAI Codex token refresh failed" / \`invalid_grant\`
|
|
1862
|
+
|
|
1863
|
+
Your refresh token is expired or rotated. Re-run one-time auth and rotate deployment secrets:
|
|
1864
|
+
|
|
1865
|
+
\`\`\`bash
|
|
1866
|
+
poncho auth login --provider openai-codex --device
|
|
1867
|
+
poncho auth export --provider openai-codex --format env
|
|
1868
|
+
\`\`\`
|
|
1869
|
+
|
|
1870
|
+
Then restart your deployment so new secrets are loaded.
|
|
1871
|
+
|
|
1872
|
+
### "Missing scopes: model.request / api.model.read / api.responses.write"
|
|
1873
|
+
|
|
1874
|
+
Re-authenticate and ensure the OAuth flow requested required scopes. Then verify:
|
|
1875
|
+
|
|
1876
|
+
\`\`\`bash
|
|
1877
|
+
poncho auth status --provider openai-codex
|
|
1878
|
+
\`\`\`
|
|
1879
|
+
|
|
1836
1880
|
### "MCP server failed to connect"
|
|
1837
1881
|
|
|
1838
1882
|
Check that:
|
|
@@ -2064,8 +2108,8 @@ var ponchoDocsTool = defineTool({
|
|
|
2064
2108
|
|
|
2065
2109
|
// src/harness.ts
|
|
2066
2110
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
2067
|
-
import { readFile as
|
|
2068
|
-
import { resolve as
|
|
2111
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
2112
|
+
import { resolve as resolve11 } from "path";
|
|
2069
2113
|
import { getTextContent as getTextContent2 } from "@poncho-ai/sdk";
|
|
2070
2114
|
|
|
2071
2115
|
// src/upload-store.ts
|
|
@@ -3572,6 +3616,252 @@ var LocalMcpBridge = class {
|
|
|
3572
3616
|
// src/model-factory.ts
|
|
3573
3617
|
import { createOpenAI } from "@ai-sdk/openai";
|
|
3574
3618
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
|
3619
|
+
|
|
3620
|
+
// src/openai-codex-auth.ts
|
|
3621
|
+
import { homedir as homedir3 } from "os";
|
|
3622
|
+
import { dirname as dirname4, resolve as resolve8 } from "path";
|
|
3623
|
+
import { mkdir as mkdir5, readFile as readFile7, chmod, writeFile as writeFile6, rm as rm3 } from "fs/promises";
|
|
3624
|
+
var OPENAI_CODEX_CLIENT_ID = "app_EMoamEEZ73f0CkXaXp7hrann";
|
|
3625
|
+
var OPENAI_AUTH_ISSUER = "https://auth.openai.com";
|
|
3626
|
+
var REFRESH_TOKEN_GRACE_MS = 5 * 60 * 1e3;
|
|
3627
|
+
var DEVICE_POLLING_SAFETY_MARGIN_MS = 3e3;
|
|
3628
|
+
var DEVICE_FLOW_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
3629
|
+
var REQUIRED_SCOPES = [
|
|
3630
|
+
"openid",
|
|
3631
|
+
"profile",
|
|
3632
|
+
"email",
|
|
3633
|
+
"offline_access",
|
|
3634
|
+
"api.responses.write",
|
|
3635
|
+
"model.request",
|
|
3636
|
+
"api.model.read"
|
|
3637
|
+
];
|
|
3638
|
+
var defaultedConfig = (config) => ({
|
|
3639
|
+
refreshTokenEnv: config?.refreshTokenEnv ?? "OPENAI_CODEX_REFRESH_TOKEN",
|
|
3640
|
+
accessTokenEnv: config?.accessTokenEnv ?? "OPENAI_CODEX_ACCESS_TOKEN",
|
|
3641
|
+
accessTokenExpiresAtEnv: config?.accessTokenExpiresAtEnv ?? "OPENAI_CODEX_ACCESS_TOKEN_EXPIRES_AT",
|
|
3642
|
+
accountIdEnv: config?.accountIdEnv ?? "OPENAI_CODEX_ACCOUNT_ID",
|
|
3643
|
+
authFilePathEnv: config?.authFilePathEnv ?? "OPENAI_CODEX_AUTH_FILE"
|
|
3644
|
+
});
|
|
3645
|
+
var parseEpochMillis = (value) => {
|
|
3646
|
+
if (!value) return void 0;
|
|
3647
|
+
const parsed = Number.parseInt(value, 10);
|
|
3648
|
+
if (Number.isNaN(parsed) || parsed <= 0) return void 0;
|
|
3649
|
+
return parsed;
|
|
3650
|
+
};
|
|
3651
|
+
var getOpenAICodexAuthFilePath = (config) => {
|
|
3652
|
+
const env = defaultedConfig(config);
|
|
3653
|
+
const fromEnv = process.env[env.authFilePathEnv];
|
|
3654
|
+
if (typeof fromEnv === "string" && fromEnv.trim().length > 0) {
|
|
3655
|
+
return resolve8(fromEnv);
|
|
3656
|
+
}
|
|
3657
|
+
return resolve8(homedir3(), ".poncho", "auth", "openai-codex.json");
|
|
3658
|
+
};
|
|
3659
|
+
var readOpenAICodexSession = async (config) => {
|
|
3660
|
+
const filePath = getOpenAICodexAuthFilePath(config);
|
|
3661
|
+
try {
|
|
3662
|
+
const content = await readFile7(filePath, "utf8");
|
|
3663
|
+
const parsed = JSON.parse(content);
|
|
3664
|
+
if (typeof parsed.refreshToken !== "string" || parsed.refreshToken.length === 0) {
|
|
3665
|
+
return void 0;
|
|
3666
|
+
}
|
|
3667
|
+
return {
|
|
3668
|
+
refreshToken: parsed.refreshToken,
|
|
3669
|
+
accessToken: typeof parsed.accessToken === "string" && parsed.accessToken.length > 0 ? parsed.accessToken : void 0,
|
|
3670
|
+
accessTokenExpiresAt: typeof parsed.accessTokenExpiresAt === "number" && parsed.accessTokenExpiresAt > 0 ? parsed.accessTokenExpiresAt : void 0,
|
|
3671
|
+
accountId: typeof parsed.accountId === "string" && parsed.accountId.length > 0 ? parsed.accountId : void 0
|
|
3672
|
+
};
|
|
3673
|
+
} catch {
|
|
3674
|
+
return void 0;
|
|
3675
|
+
}
|
|
3676
|
+
};
|
|
3677
|
+
var writeOpenAICodexSession = async (session, config) => {
|
|
3678
|
+
const filePath = getOpenAICodexAuthFilePath(config);
|
|
3679
|
+
await mkdir5(dirname4(filePath), { recursive: true });
|
|
3680
|
+
const payload = {
|
|
3681
|
+
...session,
|
|
3682
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3683
|
+
};
|
|
3684
|
+
await writeFile6(filePath, `${JSON.stringify(payload, null, 2)}
|
|
3685
|
+
`, "utf8");
|
|
3686
|
+
await chmod(filePath, 384);
|
|
3687
|
+
};
|
|
3688
|
+
var deleteOpenAICodexSession = async (config) => {
|
|
3689
|
+
const filePath = getOpenAICodexAuthFilePath(config);
|
|
3690
|
+
await rm3(filePath, { force: true });
|
|
3691
|
+
};
|
|
3692
|
+
var parseAccountIdFromJwt = (token) => {
|
|
3693
|
+
if (!token) return void 0;
|
|
3694
|
+
const parts = token.split(".");
|
|
3695
|
+
if (parts.length !== 3) return void 0;
|
|
3696
|
+
try {
|
|
3697
|
+
const claims = JSON.parse(Buffer.from(parts[1] ?? "", "base64url").toString("utf8"));
|
|
3698
|
+
return claims.chatgpt_account_id ?? claims["https://api.openai.com/auth"]?.chatgpt_account_id ?? claims.organizations?.[0]?.id;
|
|
3699
|
+
} catch {
|
|
3700
|
+
return void 0;
|
|
3701
|
+
}
|
|
3702
|
+
};
|
|
3703
|
+
var exchangeRefreshToken = async (refreshToken) => {
|
|
3704
|
+
const response = await fetch(`${OPENAI_AUTH_ISSUER}/oauth/token`, {
|
|
3705
|
+
method: "POST",
|
|
3706
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3707
|
+
body: new URLSearchParams({
|
|
3708
|
+
grant_type: "refresh_token",
|
|
3709
|
+
refresh_token: refreshToken,
|
|
3710
|
+
client_id: OPENAI_CODEX_CLIENT_ID
|
|
3711
|
+
}).toString()
|
|
3712
|
+
});
|
|
3713
|
+
if (!response.ok) {
|
|
3714
|
+
const body = await response.text();
|
|
3715
|
+
throw new Error(
|
|
3716
|
+
`OpenAI Codex token refresh failed (${response.status}). Re-run \`poncho auth login --provider openai-codex --device\`, export the new token, and update deployment secrets. Details: ${body.slice(0, 240)}`
|
|
3717
|
+
);
|
|
3718
|
+
}
|
|
3719
|
+
return await response.json();
|
|
3720
|
+
};
|
|
3721
|
+
var exchangeAuthorizationCode = async (code, codeVerifier) => {
|
|
3722
|
+
const response = await fetch(`${OPENAI_AUTH_ISSUER}/oauth/token`, {
|
|
3723
|
+
method: "POST",
|
|
3724
|
+
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
3725
|
+
body: new URLSearchParams({
|
|
3726
|
+
grant_type: "authorization_code",
|
|
3727
|
+
code,
|
|
3728
|
+
redirect_uri: `${OPENAI_AUTH_ISSUER}/deviceauth/callback`,
|
|
3729
|
+
client_id: OPENAI_CODEX_CLIENT_ID,
|
|
3730
|
+
code_verifier: codeVerifier
|
|
3731
|
+
}).toString()
|
|
3732
|
+
});
|
|
3733
|
+
if (!response.ok) {
|
|
3734
|
+
const body = await response.text();
|
|
3735
|
+
throw new Error(
|
|
3736
|
+
`OpenAI Codex device token exchange failed (${response.status}): ${body.slice(0, 240)}`
|
|
3737
|
+
);
|
|
3738
|
+
}
|
|
3739
|
+
return await response.json();
|
|
3740
|
+
};
|
|
3741
|
+
var readSessionFromEnv = (config) => {
|
|
3742
|
+
const env = defaultedConfig(config);
|
|
3743
|
+
const refreshToken = process.env[env.refreshTokenEnv];
|
|
3744
|
+
if (!refreshToken || refreshToken.trim().length === 0) return void 0;
|
|
3745
|
+
return {
|
|
3746
|
+
refreshToken: refreshToken.trim(),
|
|
3747
|
+
accessToken: process.env[env.accessTokenEnv]?.trim() || void 0,
|
|
3748
|
+
accessTokenExpiresAt: parseEpochMillis(process.env[env.accessTokenExpiresAtEnv]),
|
|
3749
|
+
accountId: process.env[env.accountIdEnv]?.trim() || void 0
|
|
3750
|
+
};
|
|
3751
|
+
};
|
|
3752
|
+
var shouldRefresh = (expiresAt) => !expiresAt || Date.now() + REFRESH_TOKEN_GRACE_MS >= expiresAt;
|
|
3753
|
+
var runtimeCachedSession;
|
|
3754
|
+
var readSession = async (config) => {
|
|
3755
|
+
const envSession = readSessionFromEnv(config);
|
|
3756
|
+
if (envSession) return { session: envSession, source: "env" };
|
|
3757
|
+
if (runtimeCachedSession) {
|
|
3758
|
+
return { session: runtimeCachedSession, source: "file" };
|
|
3759
|
+
}
|
|
3760
|
+
const fileSession = await readOpenAICodexSession(config);
|
|
3761
|
+
if (!fileSession) {
|
|
3762
|
+
throw new Error(
|
|
3763
|
+
"OpenAI Codex credentials not found. Run `poncho auth login --provider openai-codex --device` locally, or set OPENAI_CODEX_REFRESH_TOKEN in your environment."
|
|
3764
|
+
);
|
|
3765
|
+
}
|
|
3766
|
+
runtimeCachedSession = fileSession;
|
|
3767
|
+
return { session: fileSession, source: "file" };
|
|
3768
|
+
};
|
|
3769
|
+
var getOpenAICodexAccessToken = async (config) => {
|
|
3770
|
+
const { session, source } = await readSession(config);
|
|
3771
|
+
if (session.accessToken && !shouldRefresh(session.accessTokenExpiresAt)) {
|
|
3772
|
+
return { accessToken: session.accessToken, accountId: session.accountId };
|
|
3773
|
+
}
|
|
3774
|
+
const refreshed = await exchangeRefreshToken(session.refreshToken);
|
|
3775
|
+
const nextSession = {
|
|
3776
|
+
refreshToken: refreshed.refresh_token ?? session.refreshToken,
|
|
3777
|
+
accessToken: refreshed.access_token,
|
|
3778
|
+
accessTokenExpiresAt: Date.now() + (refreshed.expires_in ?? 3600) * 1e3,
|
|
3779
|
+
accountId: session.accountId ?? parseAccountIdFromJwt(refreshed.id_token) ?? parseAccountIdFromJwt(refreshed.access_token)
|
|
3780
|
+
};
|
|
3781
|
+
runtimeCachedSession = nextSession;
|
|
3782
|
+
if (source === "file") {
|
|
3783
|
+
await writeOpenAICodexSession(nextSession, config);
|
|
3784
|
+
}
|
|
3785
|
+
return {
|
|
3786
|
+
accessToken: nextSession.accessToken,
|
|
3787
|
+
accountId: nextSession.accountId
|
|
3788
|
+
};
|
|
3789
|
+
};
|
|
3790
|
+
var getOpenAICodexRequiredScopes = () => [...REQUIRED_SCOPES];
|
|
3791
|
+
var startOpenAICodexDeviceAuth = async () => {
|
|
3792
|
+
const response = await fetch(`${OPENAI_AUTH_ISSUER}/api/accounts/deviceauth/usercode`, {
|
|
3793
|
+
method: "POST",
|
|
3794
|
+
headers: {
|
|
3795
|
+
"Content-Type": "application/json",
|
|
3796
|
+
"User-Agent": "poncho/1.0"
|
|
3797
|
+
},
|
|
3798
|
+
body: JSON.stringify({
|
|
3799
|
+
client_id: OPENAI_CODEX_CLIENT_ID,
|
|
3800
|
+
scope: REQUIRED_SCOPES.join(" ")
|
|
3801
|
+
})
|
|
3802
|
+
});
|
|
3803
|
+
if (!response.ok) {
|
|
3804
|
+
const body = await response.text();
|
|
3805
|
+
throw new Error(
|
|
3806
|
+
`OpenAI Codex device authorization start failed (${response.status}): ${body.slice(0, 240)}`
|
|
3807
|
+
);
|
|
3808
|
+
}
|
|
3809
|
+
const data = await response.json();
|
|
3810
|
+
const intervalRaw = typeof data.interval === "number" ? data.interval : Number.parseInt(String(data.interval ?? "5"), 10);
|
|
3811
|
+
const intervalMs = Math.max(Number.isNaN(intervalRaw) ? 5 : intervalRaw, 1) * 1e3;
|
|
3812
|
+
return {
|
|
3813
|
+
deviceAuthId: data.device_auth_id,
|
|
3814
|
+
userCode: data.user_code,
|
|
3815
|
+
verificationUrl: `${OPENAI_AUTH_ISSUER}/codex/device`,
|
|
3816
|
+
intervalMs
|
|
3817
|
+
};
|
|
3818
|
+
};
|
|
3819
|
+
var completeOpenAICodexDeviceAuth = async (request) => {
|
|
3820
|
+
const startedAt = Date.now();
|
|
3821
|
+
while (Date.now() - startedAt < DEVICE_FLOW_TIMEOUT_MS) {
|
|
3822
|
+
const response = await fetch(`${OPENAI_AUTH_ISSUER}/api/accounts/deviceauth/token`, {
|
|
3823
|
+
method: "POST",
|
|
3824
|
+
headers: {
|
|
3825
|
+
"Content-Type": "application/json",
|
|
3826
|
+
"User-Agent": "poncho/1.0"
|
|
3827
|
+
},
|
|
3828
|
+
body: JSON.stringify({
|
|
3829
|
+
device_auth_id: request.deviceAuthId,
|
|
3830
|
+
user_code: request.userCode
|
|
3831
|
+
})
|
|
3832
|
+
});
|
|
3833
|
+
if (response.ok) {
|
|
3834
|
+
const data = await response.json();
|
|
3835
|
+
const tokens = await exchangeAuthorizationCode(
|
|
3836
|
+
data.authorization_code,
|
|
3837
|
+
data.code_verifier
|
|
3838
|
+
);
|
|
3839
|
+
if (!tokens.refresh_token || tokens.refresh_token.length === 0) {
|
|
3840
|
+
throw new Error("OpenAI Codex device auth succeeded but no refresh token was returned.");
|
|
3841
|
+
}
|
|
3842
|
+
return {
|
|
3843
|
+
refreshToken: tokens.refresh_token,
|
|
3844
|
+
accessToken: tokens.access_token,
|
|
3845
|
+
accessTokenExpiresAt: Date.now() + (tokens.expires_in ?? 3600) * 1e3,
|
|
3846
|
+
accountId: parseAccountIdFromJwt(tokens.id_token) ?? parseAccountIdFromJwt(tokens.access_token)
|
|
3847
|
+
};
|
|
3848
|
+
}
|
|
3849
|
+
if (response.status !== 403 && response.status !== 404) {
|
|
3850
|
+
const body = await response.text();
|
|
3851
|
+
throw new Error(
|
|
3852
|
+
`OpenAI Codex device authorization polling failed (${response.status}): ${body.slice(0, 240)}`
|
|
3853
|
+
);
|
|
3854
|
+
}
|
|
3855
|
+
await new Promise((resolveWait) => {
|
|
3856
|
+
setTimeout(resolveWait, request.intervalMs + DEVICE_POLLING_SAFETY_MARGIN_MS);
|
|
3857
|
+
});
|
|
3858
|
+
}
|
|
3859
|
+
throw new Error(
|
|
3860
|
+
"OpenAI Codex device authorization timed out. Re-run `poncho auth login --provider openai-codex --device`."
|
|
3861
|
+
);
|
|
3862
|
+
};
|
|
3863
|
+
|
|
3864
|
+
// src/model-factory.ts
|
|
3575
3865
|
var MODEL_CONTEXT_WINDOWS = {
|
|
3576
3866
|
"claude-opus-4-6": 2e5,
|
|
3577
3867
|
"claude-sonnet-4-6": 2e5,
|
|
@@ -3596,6 +3886,29 @@ var MODEL_CONTEXT_WINDOWS = {
|
|
|
3596
3886
|
"o3": 2e5
|
|
3597
3887
|
};
|
|
3598
3888
|
var DEFAULT_CONTEXT_WINDOW = 2e5;
|
|
3889
|
+
var OPENAI_CODEX_DEFAULT_INSTRUCTIONS = "You are Codex, based on GPT-5. You are running as a coding agent in Poncho.";
|
|
3890
|
+
var extractSystemInstructionFromInput = (input) => {
|
|
3891
|
+
if (!Array.isArray(input)) return void 0;
|
|
3892
|
+
for (const message of input) {
|
|
3893
|
+
if (!message || typeof message !== "object") continue;
|
|
3894
|
+
const candidate = message;
|
|
3895
|
+
if (candidate.role !== "system") continue;
|
|
3896
|
+
if (typeof candidate.content === "string" && candidate.content.trim().length > 0) {
|
|
3897
|
+
return candidate.content;
|
|
3898
|
+
}
|
|
3899
|
+
if (Array.isArray(candidate.content)) {
|
|
3900
|
+
const textParts = candidate.content.map((part) => {
|
|
3901
|
+
if (!part || typeof part !== "object") return "";
|
|
3902
|
+
const p = part;
|
|
3903
|
+
return typeof p.text === "string" ? p.text : "";
|
|
3904
|
+
}).filter((text) => text.trim().length > 0);
|
|
3905
|
+
if (textParts.length > 0) {
|
|
3906
|
+
return textParts.join("\n");
|
|
3907
|
+
}
|
|
3908
|
+
}
|
|
3909
|
+
}
|
|
3910
|
+
return void 0;
|
|
3911
|
+
};
|
|
3599
3912
|
var getModelContextWindow = (modelName) => {
|
|
3600
3913
|
if (MODEL_CONTEXT_WINDOWS[modelName] !== void 0) {
|
|
3601
3914
|
return MODEL_CONTEXT_WINDOWS[modelName];
|
|
@@ -3610,6 +3923,39 @@ var getModelContextWindow = (modelName) => {
|
|
|
3610
3923
|
};
|
|
3611
3924
|
var createModelProvider = (provider, config) => {
|
|
3612
3925
|
const normalized = (provider ?? "anthropic").toLowerCase();
|
|
3926
|
+
if (normalized === "openai-codex") {
|
|
3927
|
+
const openai = createOpenAI({
|
|
3928
|
+
apiKey: "oauth-placeholder",
|
|
3929
|
+
fetch: async (input, init) => {
|
|
3930
|
+
const { accessToken, accountId } = await getOpenAICodexAccessToken(config?.openaiCodex);
|
|
3931
|
+
const headers = new Headers(init?.headers);
|
|
3932
|
+
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
3933
|
+
headers.set("originator", "poncho");
|
|
3934
|
+
headers.set("User-Agent", "poncho/1.0");
|
|
3935
|
+
if (accountId) {
|
|
3936
|
+
headers.set("ChatGPT-Account-Id", accountId);
|
|
3937
|
+
}
|
|
3938
|
+
const originalUrl = input instanceof URL ? input.toString() : typeof input === "string" ? input : input.url;
|
|
3939
|
+
const parsed = new URL(originalUrl);
|
|
3940
|
+
const shouldRewrite = parsed.pathname.includes("/v1/responses") || parsed.pathname.includes("/chat/completions");
|
|
3941
|
+
const targetUrl = shouldRewrite ? "https://chatgpt.com/backend-api/codex/responses" : originalUrl;
|
|
3942
|
+
let body = init?.body;
|
|
3943
|
+
if (shouldRewrite && typeof body === "string" && headers.get("Content-Type")?.includes("application/json")) {
|
|
3944
|
+
try {
|
|
3945
|
+
const payload = JSON.parse(body);
|
|
3946
|
+
if (typeof payload.instructions !== "string" || payload.instructions.trim() === "") {
|
|
3947
|
+
payload.instructions = extractSystemInstructionFromInput(payload.input) ?? OPENAI_CODEX_DEFAULT_INSTRUCTIONS;
|
|
3948
|
+
}
|
|
3949
|
+
payload.store = false;
|
|
3950
|
+
body = JSON.stringify(payload);
|
|
3951
|
+
} catch {
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
return fetch(targetUrl, { ...init, headers, body });
|
|
3955
|
+
}
|
|
3956
|
+
});
|
|
3957
|
+
return (modelName) => openai(modelName);
|
|
3958
|
+
}
|
|
3613
3959
|
if (normalized === "openai") {
|
|
3614
3960
|
const apiKeyEnv2 = config?.openai?.apiKeyEnv ?? "OPENAI_API_KEY";
|
|
3615
3961
|
const openai = createOpenAI({
|
|
@@ -3626,8 +3972,8 @@ var createModelProvider = (provider, config) => {
|
|
|
3626
3972
|
};
|
|
3627
3973
|
|
|
3628
3974
|
// src/skill-context.ts
|
|
3629
|
-
import { readFile as
|
|
3630
|
-
import { dirname as
|
|
3975
|
+
import { readFile as readFile8, readdir as readdir2, stat } from "fs/promises";
|
|
3976
|
+
import { dirname as dirname5, resolve as resolve9, normalize } from "path";
|
|
3631
3977
|
import YAML3 from "yaml";
|
|
3632
3978
|
var DEFAULT_SKILL_DIRS = ["skills"];
|
|
3633
3979
|
var resolveSkillDirs = (workingDir, extraPaths) => {
|
|
@@ -3639,7 +3985,7 @@ var resolveSkillDirs = (workingDir, extraPaths) => {
|
|
|
3639
3985
|
}
|
|
3640
3986
|
}
|
|
3641
3987
|
}
|
|
3642
|
-
return dirs.map((d) =>
|
|
3988
|
+
return dirs.map((d) => resolve9(workingDir, d));
|
|
3643
3989
|
};
|
|
3644
3990
|
var FRONTMATTER_PATTERN3 = /^---\s*\n([\s\S]*?)\n---\s*\n?([\s\S]*)$/;
|
|
3645
3991
|
var asRecord2 = (value) => typeof value === "object" && value !== null ? value : {};
|
|
@@ -3708,7 +4054,7 @@ var collectSkillManifests = async (directory) => {
|
|
|
3708
4054
|
const entries = await readdir2(directory, { withFileTypes: true });
|
|
3709
4055
|
const files = [];
|
|
3710
4056
|
for (const entry of entries) {
|
|
3711
|
-
const fullPath =
|
|
4057
|
+
const fullPath = resolve9(directory, entry.name);
|
|
3712
4058
|
let isDir = entry.isDirectory();
|
|
3713
4059
|
let isFile = entry.isFile();
|
|
3714
4060
|
if (entry.isSymbolicLink()) {
|
|
@@ -3743,13 +4089,13 @@ var loadSkillMetadata = async (workingDir, extraSkillPaths) => {
|
|
|
3743
4089
|
const seen = /* @__PURE__ */ new Set();
|
|
3744
4090
|
for (const manifest of allManifests) {
|
|
3745
4091
|
try {
|
|
3746
|
-
const content = await
|
|
4092
|
+
const content = await readFile8(manifest, "utf8");
|
|
3747
4093
|
const parsed = parseSkillFrontmatter(content);
|
|
3748
4094
|
if (parsed && !seen.has(parsed.name)) {
|
|
3749
4095
|
seen.add(parsed.name);
|
|
3750
4096
|
skills.push({
|
|
3751
4097
|
...parsed,
|
|
3752
|
-
skillDir:
|
|
4098
|
+
skillDir: dirname5(manifest),
|
|
3753
4099
|
skillPath: manifest
|
|
3754
4100
|
});
|
|
3755
4101
|
}
|
|
@@ -3788,7 +4134,7 @@ ${xmlSkills}
|
|
|
3788
4134
|
};
|
|
3789
4135
|
var escapeXml = (value) => value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
3790
4136
|
var loadSkillInstructions = async (skill) => {
|
|
3791
|
-
const content = await
|
|
4137
|
+
const content = await readFile8(skill.skillPath, "utf8");
|
|
3792
4138
|
const match = content.match(FRONTMATTER_PATTERN3);
|
|
3793
4139
|
return match ? match[2].trim() : content.trim();
|
|
3794
4140
|
};
|
|
@@ -3797,11 +4143,11 @@ var readSkillResource = async (skill, relativePath) => {
|
|
|
3797
4143
|
if (normalized.startsWith("..") || normalized.startsWith("/")) {
|
|
3798
4144
|
throw new Error("Path must be relative and within the skill directory");
|
|
3799
4145
|
}
|
|
3800
|
-
const fullPath =
|
|
4146
|
+
const fullPath = resolve9(skill.skillDir, normalized);
|
|
3801
4147
|
if (!fullPath.startsWith(skill.skillDir)) {
|
|
3802
4148
|
throw new Error("Path escapes the skill directory");
|
|
3803
4149
|
}
|
|
3804
|
-
return await
|
|
4150
|
+
return await readFile8(fullPath, "utf8");
|
|
3805
4151
|
};
|
|
3806
4152
|
var MAX_INSTRUCTIONS_PER_SKILL = 1200;
|
|
3807
4153
|
var loadSkillContext = async (workingDir) => {
|
|
@@ -3920,7 +4266,7 @@ function convertSchema(schema) {
|
|
|
3920
4266
|
// src/skill-tools.ts
|
|
3921
4267
|
import { defineTool as defineTool4 } from "@poncho-ai/sdk";
|
|
3922
4268
|
import { access as access2, readdir as readdir3, stat as stat2 } from "fs/promises";
|
|
3923
|
-
import { extname, normalize as normalize2, resolve as
|
|
4269
|
+
import { extname, normalize as normalize2, resolve as resolve10, sep as sep2 } from "path";
|
|
3924
4270
|
import { pathToFileURL } from "url";
|
|
3925
4271
|
import { createJiti as createJiti2 } from "jiti";
|
|
3926
4272
|
var createSkillTools = (skills, options) => {
|
|
@@ -4178,7 +4524,7 @@ var collectScriptFiles = async (directory) => {
|
|
|
4178
4524
|
if (entry.name === "node_modules") {
|
|
4179
4525
|
continue;
|
|
4180
4526
|
}
|
|
4181
|
-
const fullPath =
|
|
4527
|
+
const fullPath = resolve10(directory, entry.name);
|
|
4182
4528
|
let isDir = entry.isDirectory();
|
|
4183
4529
|
let isFile = entry.isFile();
|
|
4184
4530
|
if (entry.isSymbolicLink()) {
|
|
@@ -4217,8 +4563,8 @@ var normalizeScriptPolicyPath = (relativePath) => {
|
|
|
4217
4563
|
};
|
|
4218
4564
|
var resolveScriptPath = (baseDir, relativePath, containmentDir) => {
|
|
4219
4565
|
const normalized = normalizeScriptPolicyPath(relativePath);
|
|
4220
|
-
const fullPath =
|
|
4221
|
-
const boundary =
|
|
4566
|
+
const fullPath = resolve10(baseDir, normalized);
|
|
4567
|
+
const boundary = resolve10(containmentDir ?? baseDir);
|
|
4222
4568
|
if (!fullPath.startsWith(`${boundary}${sep2}`) && fullPath !== boundary) {
|
|
4223
4569
|
throw new Error("Script path must stay inside the allowed directory");
|
|
4224
4570
|
}
|
|
@@ -4324,8 +4670,8 @@ function applyRateLimitCooldown(retryAfterHeader) {
|
|
|
4324
4670
|
async function runWithSearchThrottle(fn) {
|
|
4325
4671
|
const previous = searchQueue;
|
|
4326
4672
|
let release;
|
|
4327
|
-
searchQueue = new Promise((
|
|
4328
|
-
release =
|
|
4673
|
+
searchQueue = new Promise((resolve13) => {
|
|
4674
|
+
release = resolve13;
|
|
4329
4675
|
});
|
|
4330
4676
|
await previous.catch(() => {
|
|
4331
4677
|
});
|
|
@@ -5122,6 +5468,8 @@ export default {
|
|
|
5122
5468
|
providers: {
|
|
5123
5469
|
anthropic: { apiKeyEnv: "ANTHROPIC_API_KEY" },
|
|
5124
5470
|
openai: { apiKeyEnv: "OPENAI_API_KEY" },
|
|
5471
|
+
// openai-codex provider reads OAuth tokens from env vars by default:
|
|
5472
|
+
// openaiCodex: { refreshTokenEnv: "OPENAI_CODEX_REFRESH_TOKEN", accountIdEnv: "OPENAI_CODEX_ACCOUNT_ID" },
|
|
5125
5473
|
},
|
|
5126
5474
|
auth: {
|
|
5127
5475
|
required: true,
|
|
@@ -5524,8 +5872,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
5524
5872
|
return false;
|
|
5525
5873
|
}
|
|
5526
5874
|
try {
|
|
5527
|
-
const agentFilePath =
|
|
5528
|
-
const rawContent = await
|
|
5875
|
+
const agentFilePath = resolve11(this.workingDir, "AGENT.md");
|
|
5876
|
+
const rawContent = await readFile9(agentFilePath, "utf8");
|
|
5529
5877
|
if (rawContent === this.agentFileFingerprint) {
|
|
5530
5878
|
return false;
|
|
5531
5879
|
}
|
|
@@ -5594,8 +5942,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
5594
5942
|
}
|
|
5595
5943
|
}
|
|
5596
5944
|
async initialize() {
|
|
5597
|
-
const agentFilePath =
|
|
5598
|
-
const agentRawContent = await
|
|
5945
|
+
const agentFilePath = resolve11(this.workingDir, "AGENT.md");
|
|
5946
|
+
const agentRawContent = await readFile9(agentFilePath, "utf8");
|
|
5599
5947
|
this.parsedAgent = parseAgentMarkdown(agentRawContent);
|
|
5600
5948
|
this.agentFileFingerprint = agentRawContent;
|
|
5601
5949
|
const identity = await ensureAgentIdentity(this.workingDir);
|
|
@@ -5702,14 +6050,14 @@ var AgentHarness = class _AgentHarness {
|
|
|
5702
6050
|
const filePath = pathResolve(stateDir, `${sessionId}.json`);
|
|
5703
6051
|
return {
|
|
5704
6052
|
async save(json) {
|
|
5705
|
-
const { mkdir:
|
|
5706
|
-
await
|
|
5707
|
-
await
|
|
6053
|
+
const { mkdir: mkdir7, writeFile: writeFile8 } = await import("fs/promises");
|
|
6054
|
+
await mkdir7(stateDir, { recursive: true });
|
|
6055
|
+
await writeFile8(filePath, json, "utf8");
|
|
5708
6056
|
},
|
|
5709
6057
|
async load() {
|
|
5710
|
-
const { readFile:
|
|
6058
|
+
const { readFile: readFile11 } = await import("fs/promises");
|
|
5711
6059
|
try {
|
|
5712
|
-
return await
|
|
6060
|
+
return await readFile11(filePath, "utf8");
|
|
5713
6061
|
} catch {
|
|
5714
6062
|
return void 0;
|
|
5715
6063
|
}
|
|
@@ -5775,7 +6123,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
5775
6123
|
let browserMod;
|
|
5776
6124
|
try {
|
|
5777
6125
|
const { existsSync } = await import("fs");
|
|
5778
|
-
const { join, dirname:
|
|
6126
|
+
const { join, dirname: dirname7 } = await import("path");
|
|
5779
6127
|
const { pathToFileURL: pathToFileURL2 } = await import("url");
|
|
5780
6128
|
let searchDir = this.workingDir;
|
|
5781
6129
|
let entryPath;
|
|
@@ -5785,7 +6133,7 @@ var AgentHarness = class _AgentHarness {
|
|
|
5785
6133
|
entryPath = candidate;
|
|
5786
6134
|
break;
|
|
5787
6135
|
}
|
|
5788
|
-
const parent =
|
|
6136
|
+
const parent = dirname7(searchDir);
|
|
5789
6137
|
if (parent === searchDir) break;
|
|
5790
6138
|
searchDir = parent;
|
|
5791
6139
|
}
|
|
@@ -5890,9 +6238,9 @@ var AgentHarness = class _AgentHarness {
|
|
|
5890
6238
|
for await (const event of this.run(input)) {
|
|
5891
6239
|
eventQueue.push(event);
|
|
5892
6240
|
if (queueResolve) {
|
|
5893
|
-
const
|
|
6241
|
+
const resolve13 = queueResolve;
|
|
5894
6242
|
queueResolve = null;
|
|
5895
|
-
|
|
6243
|
+
resolve13();
|
|
5896
6244
|
}
|
|
5897
6245
|
}
|
|
5898
6246
|
} catch (error) {
|
|
@@ -5911,8 +6259,8 @@ var AgentHarness = class _AgentHarness {
|
|
|
5911
6259
|
if (eventQueue.length > 0) {
|
|
5912
6260
|
yield eventQueue.shift();
|
|
5913
6261
|
} else if (!generatorDone) {
|
|
5914
|
-
await new Promise((
|
|
5915
|
-
queueResolve =
|
|
6262
|
+
await new Promise((resolve13) => {
|
|
6263
|
+
queueResolve = resolve13;
|
|
5916
6264
|
});
|
|
5917
6265
|
}
|
|
5918
6266
|
}
|
|
@@ -6131,7 +6479,7 @@ ${this.skillFingerprint}`;
|
|
|
6131
6479
|
if (lastMsg && lastMsg.role !== "user") {
|
|
6132
6480
|
messages.push({
|
|
6133
6481
|
role: "user",
|
|
6134
|
-
content: "[System: Your previous turn was interrupted by a time limit. Continue from where you left off \u2014 do NOT repeat
|
|
6482
|
+
content: "[System: Your previous turn was interrupted by a time limit. Your partial response above is already visible to the user. Continue EXACTLY from where you left off \u2014 do NOT restart, re-summarize, or repeat any content you already produced. If you were mid-sentence or mid-table, continue that sentence or table. Proceed directly with the next action or output.]",
|
|
6135
6483
|
metadata: { timestamp: now(), id: randomUUID3() }
|
|
6136
6484
|
});
|
|
6137
6485
|
}
|
|
@@ -6461,7 +6809,10 @@ ${textContent}` };
|
|
|
6461
6809
|
let chunkCount = 0;
|
|
6462
6810
|
const hasRunTimeout = timeoutMs > 0;
|
|
6463
6811
|
const streamDeadline = hasRunTimeout ? start + timeoutMs : 0;
|
|
6812
|
+
const hasSoftDeadline = softDeadlineMs > 0;
|
|
6813
|
+
const INTER_CHUNK_TIMEOUT_MS = 6e4;
|
|
6464
6814
|
const fullStreamIterator = result.fullStream[Symbol.asyncIterator]();
|
|
6815
|
+
let softDeadlineFiredDuringStream = false;
|
|
6465
6816
|
try {
|
|
6466
6817
|
while (true) {
|
|
6467
6818
|
if (isCancelled()) {
|
|
@@ -6469,8 +6820,8 @@ ${textContent}` };
|
|
|
6469
6820
|
return;
|
|
6470
6821
|
}
|
|
6471
6822
|
if (hasRunTimeout) {
|
|
6472
|
-
const
|
|
6473
|
-
if (
|
|
6823
|
+
const remaining = streamDeadline - now();
|
|
6824
|
+
if (remaining <= 0) {
|
|
6474
6825
|
yield pushEvent({
|
|
6475
6826
|
type: "run:error",
|
|
6476
6827
|
runId,
|
|
@@ -6485,22 +6836,33 @@ ${textContent}` };
|
|
|
6485
6836
|
return;
|
|
6486
6837
|
}
|
|
6487
6838
|
}
|
|
6488
|
-
|
|
6489
|
-
|
|
6839
|
+
if (hasSoftDeadline && chunkCount > 0 && now() - start >= softDeadlineMs) {
|
|
6840
|
+
softDeadlineFiredDuringStream = true;
|
|
6841
|
+
break;
|
|
6842
|
+
}
|
|
6843
|
+
const hardRemaining = hasRunTimeout ? streamDeadline - now() : Infinity;
|
|
6844
|
+
const softRemaining = hasSoftDeadline ? Math.max(0, start + softDeadlineMs - now()) : Infinity;
|
|
6845
|
+
const deadlineRemaining = Math.min(hardRemaining, softRemaining);
|
|
6846
|
+
const timeout = chunkCount === 0 ? Math.min(deadlineRemaining, FIRST_CHUNK_TIMEOUT_MS) : Math.min(deadlineRemaining, INTER_CHUNK_TIMEOUT_MS);
|
|
6490
6847
|
let nextPart;
|
|
6491
|
-
if (timeout <= 0 && chunkCount > 0) {
|
|
6848
|
+
if (timeout <= 0 && chunkCount > 0 && !hasSoftDeadline) {
|
|
6492
6849
|
nextPart = await fullStreamIterator.next();
|
|
6493
6850
|
} else {
|
|
6851
|
+
const effectiveTimeout = Math.max(timeout, 1);
|
|
6494
6852
|
let timer;
|
|
6495
6853
|
nextPart = await Promise.race([
|
|
6496
6854
|
fullStreamIterator.next(),
|
|
6497
|
-
new Promise((
|
|
6498
|
-
timer = setTimeout(() =>
|
|
6855
|
+
new Promise((resolve13) => {
|
|
6856
|
+
timer = setTimeout(() => resolve13(null), effectiveTimeout);
|
|
6499
6857
|
})
|
|
6500
6858
|
]);
|
|
6501
6859
|
clearTimeout(timer);
|
|
6502
6860
|
}
|
|
6503
6861
|
if (nextPart === null) {
|
|
6862
|
+
if (hasSoftDeadline && deadlineRemaining <= INTER_CHUNK_TIMEOUT_MS) {
|
|
6863
|
+
softDeadlineFiredDuringStream = true;
|
|
6864
|
+
break;
|
|
6865
|
+
}
|
|
6504
6866
|
const isFirstChunk = chunkCount === 0;
|
|
6505
6867
|
console.error(
|
|
6506
6868
|
`[poncho][harness] Stream timeout waiting for ${isFirstChunk ? "first" : "next"} chunk: model="${modelName}", step=${step}, chunks=${chunkCount}, elapsed=${now() - start}ms`
|
|
@@ -6533,11 +6895,42 @@ ${textContent}` };
|
|
|
6533
6895
|
fullStreamIterator.return?.(void 0)?.catch?.(() => {
|
|
6534
6896
|
});
|
|
6535
6897
|
}
|
|
6898
|
+
if (softDeadlineFiredDuringStream) {
|
|
6899
|
+
if (fullText.length > 0) {
|
|
6900
|
+
messages.push({
|
|
6901
|
+
role: "assistant",
|
|
6902
|
+
content: fullText,
|
|
6903
|
+
metadata: { timestamp: now(), id: randomUUID3(), step }
|
|
6904
|
+
});
|
|
6905
|
+
}
|
|
6906
|
+
const result_ = {
|
|
6907
|
+
status: "completed",
|
|
6908
|
+
response: responseText + fullText,
|
|
6909
|
+
steps: step,
|
|
6910
|
+
tokens: { input: totalInputTokens, output: totalOutputTokens, cached: totalCachedTokens },
|
|
6911
|
+
duration: now() - start,
|
|
6912
|
+
continuation: true,
|
|
6913
|
+
continuationMessages: [...messages],
|
|
6914
|
+
maxSteps,
|
|
6915
|
+
contextTokens: latestContextTokens + toolOutputEstimateSinceModel,
|
|
6916
|
+
contextWindow
|
|
6917
|
+
};
|
|
6918
|
+
console.info(`[poncho][harness] Soft deadline fired mid-stream at step ${step} (${(now() - start).toFixed(0)}ms). Checkpointing with ${fullText.length} chars of partial text.`);
|
|
6919
|
+
yield pushEvent({ type: "run:completed", runId, result: result_ });
|
|
6920
|
+
return;
|
|
6921
|
+
}
|
|
6536
6922
|
if (isCancelled()) {
|
|
6537
6923
|
yield emitCancellation();
|
|
6538
6924
|
return;
|
|
6539
6925
|
}
|
|
6540
6926
|
if (softDeadlineMs > 0 && now() - start > softDeadlineMs) {
|
|
6927
|
+
if (fullText.length > 0) {
|
|
6928
|
+
messages.push({
|
|
6929
|
+
role: "assistant",
|
|
6930
|
+
content: fullText,
|
|
6931
|
+
metadata: { timestamp: now(), id: randomUUID3(), step }
|
|
6932
|
+
});
|
|
6933
|
+
}
|
|
6541
6934
|
const result_ = {
|
|
6542
6935
|
status: "completed",
|
|
6543
6936
|
response: responseText + fullText,
|
|
@@ -6777,7 +7170,7 @@ ${textContent}` };
|
|
|
6777
7170
|
const raced = await Promise.race([
|
|
6778
7171
|
this.dispatcher.executeBatch(approvedCalls, toolContext),
|
|
6779
7172
|
new Promise(
|
|
6780
|
-
(
|
|
7173
|
+
(resolve13) => setTimeout(() => resolve13(TOOL_DEADLINE_SENTINEL), toolDeadlineRemainingMs)
|
|
6781
7174
|
)
|
|
6782
7175
|
]);
|
|
6783
7176
|
if (raced === TOOL_DEADLINE_SENTINEL) {
|
|
@@ -6789,6 +7182,13 @@ ${textContent}` };
|
|
|
6789
7182
|
batchResults = await this.dispatcher.executeBatch(approvedCalls, toolContext);
|
|
6790
7183
|
}
|
|
6791
7184
|
if (batchResults === TOOL_DEADLINE_SENTINEL) {
|
|
7185
|
+
if (fullText.length > 0) {
|
|
7186
|
+
messages.push({
|
|
7187
|
+
role: "assistant",
|
|
7188
|
+
content: fullText,
|
|
7189
|
+
metadata: { timestamp: now(), id: randomUUID3(), step }
|
|
7190
|
+
});
|
|
7191
|
+
}
|
|
6792
7192
|
const result_ = {
|
|
6793
7193
|
status: "completed",
|
|
6794
7194
|
response: responseText + fullText,
|
|
@@ -7111,8 +7511,8 @@ var LatitudeCapture = class {
|
|
|
7111
7511
|
|
|
7112
7512
|
// src/state.ts
|
|
7113
7513
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
7114
|
-
import { mkdir as
|
|
7115
|
-
import { dirname as
|
|
7514
|
+
import { mkdir as mkdir6, readFile as readFile10, readdir as readdir4, rename as rename3, rm as rm4, writeFile as writeFile7 } from "fs/promises";
|
|
7515
|
+
import { dirname as dirname6, resolve as resolve12 } from "path";
|
|
7116
7516
|
var DEFAULT_OWNER = "local-owner";
|
|
7117
7517
|
var LOCAL_STATE_FILE = "state.json";
|
|
7118
7518
|
var CONVERSATIONS_DIRECTORY = "conversations";
|
|
@@ -7128,9 +7528,9 @@ var toStoreIdentity = async ({
|
|
|
7128
7528
|
return { name: ensured.name, id: agentId };
|
|
7129
7529
|
};
|
|
7130
7530
|
var writeJsonAtomic3 = async (filePath, payload) => {
|
|
7131
|
-
await
|
|
7531
|
+
await mkdir6(dirname6(filePath), { recursive: true });
|
|
7132
7532
|
const tmpPath = `${filePath}.tmp`;
|
|
7133
|
-
await
|
|
7533
|
+
await writeFile7(tmpPath, JSON.stringify(payload, null, 2), "utf8");
|
|
7134
7534
|
await rename3(tmpPath, filePath);
|
|
7135
7535
|
};
|
|
7136
7536
|
var formatUtcTimestamp = (value) => new Date(value).toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
@@ -7319,8 +7719,8 @@ var FileConversationStore = class {
|
|
|
7319
7719
|
agentId: this.agentId
|
|
7320
7720
|
});
|
|
7321
7721
|
const agentDir = getAgentStoreDirectory(identity);
|
|
7322
|
-
const conversationsDir =
|
|
7323
|
-
const indexPath =
|
|
7722
|
+
const conversationsDir = resolve12(agentDir, CONVERSATIONS_DIRECTORY);
|
|
7723
|
+
const indexPath = resolve12(conversationsDir, LOCAL_CONVERSATION_INDEX_FILE);
|
|
7324
7724
|
this.paths = { conversationsDir, indexPath };
|
|
7325
7725
|
return this.paths;
|
|
7326
7726
|
}
|
|
@@ -7334,9 +7734,9 @@ var FileConversationStore = class {
|
|
|
7334
7734
|
}
|
|
7335
7735
|
async readConversationFile(fileName) {
|
|
7336
7736
|
const { conversationsDir } = await this.resolvePaths();
|
|
7337
|
-
const filePath =
|
|
7737
|
+
const filePath = resolve12(conversationsDir, fileName);
|
|
7338
7738
|
try {
|
|
7339
|
-
const raw = await
|
|
7739
|
+
const raw = await readFile10(filePath, "utf8");
|
|
7340
7740
|
return JSON.parse(raw);
|
|
7341
7741
|
} catch {
|
|
7342
7742
|
return void 0;
|
|
@@ -7382,7 +7782,7 @@ var FileConversationStore = class {
|
|
|
7382
7782
|
this.loaded = true;
|
|
7383
7783
|
const { indexPath } = await this.resolvePaths();
|
|
7384
7784
|
try {
|
|
7385
|
-
const raw = await
|
|
7785
|
+
const raw = await readFile10(indexPath, "utf8");
|
|
7386
7786
|
const parsed = JSON.parse(raw);
|
|
7387
7787
|
for (const conversation of parsed.conversations ?? []) {
|
|
7388
7788
|
this.conversations.set(conversation.conversationId, conversation);
|
|
@@ -7405,7 +7805,7 @@ var FileConversationStore = class {
|
|
|
7405
7805
|
const { conversationsDir } = await this.resolvePaths();
|
|
7406
7806
|
const existing = this.conversations.get(conversation.conversationId);
|
|
7407
7807
|
const fileName = existing?.fileName ?? this.resolveConversationFileName(conversation);
|
|
7408
|
-
const filePath =
|
|
7808
|
+
const filePath = resolve12(conversationsDir, fileName);
|
|
7409
7809
|
this.writing = this.writing.then(async () => {
|
|
7410
7810
|
await writeJsonAtomic3(filePath, conversation);
|
|
7411
7811
|
this.conversations.set(conversation.conversationId, {
|
|
@@ -7503,7 +7903,7 @@ var FileConversationStore = class {
|
|
|
7503
7903
|
if (removed) {
|
|
7504
7904
|
this.writing = this.writing.then(async () => {
|
|
7505
7905
|
if (existing) {
|
|
7506
|
-
await
|
|
7906
|
+
await rm4(resolve12(conversationsDir, existing.fileName), { force: true });
|
|
7507
7907
|
}
|
|
7508
7908
|
await this.writeIndex();
|
|
7509
7909
|
});
|
|
@@ -7525,7 +7925,7 @@ var FileConversationStore = class {
|
|
|
7525
7925
|
const summary = this.conversations.get(conversationId);
|
|
7526
7926
|
if (!summary) return void 0;
|
|
7527
7927
|
const { conversationsDir } = await this.resolvePaths();
|
|
7528
|
-
const filePath =
|
|
7928
|
+
const filePath = resolve12(conversationsDir, summary.fileName);
|
|
7529
7929
|
let result;
|
|
7530
7930
|
this.writing = this.writing.then(async () => {
|
|
7531
7931
|
const conv = await this.readConversationFile(summary.fileName);
|
|
@@ -7564,7 +7964,7 @@ var FileStateStore = class {
|
|
|
7564
7964
|
workingDir: this.workingDir,
|
|
7565
7965
|
agentId: this.agentId
|
|
7566
7966
|
});
|
|
7567
|
-
this.filePath =
|
|
7967
|
+
this.filePath = resolve12(getAgentStoreDirectory(identity), LOCAL_STATE_FILE);
|
|
7568
7968
|
}
|
|
7569
7969
|
isExpired(state) {
|
|
7570
7970
|
return typeof this.ttlMs === "number" && Date.now() - state.updatedAt > this.ttlMs;
|
|
@@ -7576,7 +7976,7 @@ var FileStateStore = class {
|
|
|
7576
7976
|
}
|
|
7577
7977
|
this.loaded = true;
|
|
7578
7978
|
try {
|
|
7579
|
-
const raw = await
|
|
7979
|
+
const raw = await readFile10(this.filePath, "utf8");
|
|
7580
7980
|
const parsed = JSON.parse(raw);
|
|
7581
7981
|
for (const state of parsed.states ?? []) {
|
|
7582
7982
|
this.states.set(state.runId, state);
|
|
@@ -8285,6 +8685,7 @@ export {
|
|
|
8285
8685
|
LatitudeCapture,
|
|
8286
8686
|
LocalMcpBridge,
|
|
8287
8687
|
LocalUploadStore,
|
|
8688
|
+
OPENAI_CODEX_CLIENT_ID,
|
|
8288
8689
|
PONCHO_UPLOAD_SCHEME,
|
|
8289
8690
|
S3UploadStore,
|
|
8290
8691
|
STORAGE_SCHEMA_VERSION,
|
|
@@ -8294,6 +8695,7 @@ export {
|
|
|
8294
8695
|
buildAgentDirectoryName,
|
|
8295
8696
|
buildSkillContextWindow,
|
|
8296
8697
|
compactMessages,
|
|
8698
|
+
completeOpenAICodexDeviceAuth,
|
|
8297
8699
|
createConversationStore,
|
|
8298
8700
|
createDefaultTools,
|
|
8299
8701
|
createDeleteDirectoryTool,
|
|
@@ -8309,6 +8711,7 @@ export {
|
|
|
8309
8711
|
createUploadStore,
|
|
8310
8712
|
createWriteTool,
|
|
8311
8713
|
defineTool7 as defineTool,
|
|
8714
|
+
deleteOpenAICodexSession,
|
|
8312
8715
|
deriveUploadKey,
|
|
8313
8716
|
ensureAgentIdentity,
|
|
8314
8717
|
estimateTokens,
|
|
@@ -8317,6 +8720,9 @@ export {
|
|
|
8317
8720
|
generateAgentId,
|
|
8318
8721
|
getAgentStoreDirectory,
|
|
8319
8722
|
getModelContextWindow,
|
|
8723
|
+
getOpenAICodexAccessToken,
|
|
8724
|
+
getOpenAICodexAuthFilePath,
|
|
8725
|
+
getOpenAICodexRequiredScopes,
|
|
8320
8726
|
getPonchoStoreRoot,
|
|
8321
8727
|
jsonSchemaToZod,
|
|
8322
8728
|
loadPonchoConfig,
|
|
@@ -8328,6 +8734,7 @@ export {
|
|
|
8328
8734
|
parseAgentFile,
|
|
8329
8735
|
parseAgentMarkdown,
|
|
8330
8736
|
ponchoDocsTool,
|
|
8737
|
+
readOpenAICodexSession,
|
|
8331
8738
|
readSkillResource,
|
|
8332
8739
|
renderAgentPrompt,
|
|
8333
8740
|
resolveAgentIdentity,
|
|
@@ -8335,5 +8742,7 @@ export {
|
|
|
8335
8742
|
resolveMemoryConfig,
|
|
8336
8743
|
resolveSkillDirs,
|
|
8337
8744
|
resolveStateConfig,
|
|
8338
|
-
slugifyStorageComponent
|
|
8745
|
+
slugifyStorageComponent,
|
|
8746
|
+
startOpenAICodexDeviceAuth,
|
|
8747
|
+
writeOpenAICodexSession
|
|
8339
8748
|
};
|