@poncho-ai/cli 0.11.1 → 0.13.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 +6 -6
- package/CHANGELOG.md +24 -0
- package/dist/{chunk-T2F6ICXI.js → chunk-CUCEDHME.js} +542 -18
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +6 -1
- package/dist/index.js +1 -1
- package/dist/{run-interactive-ink-7FP5PT7Q.js → run-interactive-ink-VZBOYJYS.js} +1 -1
- package/package.json +5 -3
- package/src/index.ts +594 -19
- package/src/init-feature-context.ts +6 -0
- package/src/init-onboarding.ts +10 -2
- package/test/cli.test.ts +42 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import { spawn } from "child_process";
|
|
3
3
|
import { access as access2, cp, mkdir as mkdir3, readFile as readFile3, readdir, rm, stat, writeFile as writeFile3 } from "fs/promises";
|
|
4
|
-
import { existsSync } from "fs";
|
|
4
|
+
import { existsSync, watch as fsWatch } from "fs";
|
|
5
5
|
import {
|
|
6
6
|
createServer
|
|
7
7
|
} from "http";
|
|
@@ -18,9 +18,14 @@ import {
|
|
|
18
18
|
ensureAgentIdentity as ensureAgentIdentity2,
|
|
19
19
|
generateAgentId,
|
|
20
20
|
loadPonchoConfig,
|
|
21
|
+
parseAgentMarkdown,
|
|
21
22
|
resolveStateConfig
|
|
22
23
|
} from "@poncho-ai/harness";
|
|
23
24
|
import { getTextContent } from "@poncho-ai/sdk";
|
|
25
|
+
import {
|
|
26
|
+
AgentBridge,
|
|
27
|
+
SlackAdapter
|
|
28
|
+
} from "@poncho-ai/messaging";
|
|
24
29
|
import Busboy from "busboy";
|
|
25
30
|
import { Command } from "commander";
|
|
26
31
|
import dotenv from "dotenv";
|
|
@@ -3508,12 +3513,17 @@ var buildConfigFromOnboardingAnswers = (answers) => {
|
|
|
3508
3513
|
enabled: telemetryEnabled
|
|
3509
3514
|
};
|
|
3510
3515
|
maybeSet(telemetry, "otlp", answers["telemetry.otlp"]);
|
|
3511
|
-
|
|
3516
|
+
const messagingPlatform = String(answers["messaging.platform"] ?? "none");
|
|
3517
|
+
const config = {
|
|
3512
3518
|
mcp: [],
|
|
3513
3519
|
auth,
|
|
3514
3520
|
storage,
|
|
3515
3521
|
telemetry
|
|
3516
3522
|
};
|
|
3523
|
+
if (messagingPlatform !== "none") {
|
|
3524
|
+
config.messaging = [{ platform: messagingPlatform }];
|
|
3525
|
+
}
|
|
3526
|
+
return config;
|
|
3517
3527
|
};
|
|
3518
3528
|
var collectEnvVars = (answers) => {
|
|
3519
3529
|
const envVars = /* @__PURE__ */ new Set();
|
|
@@ -3657,11 +3667,13 @@ var summarizeConfig = (config) => {
|
|
|
3657
3667
|
const memoryEnabled = config?.storage?.memory?.enabled ?? config?.memory?.enabled ?? false;
|
|
3658
3668
|
const authRequired = config?.auth?.required ?? false;
|
|
3659
3669
|
const telemetryEnabled = config?.telemetry?.enabled ?? true;
|
|
3670
|
+
const messagingPlatforms = (config?.messaging ?? []).map((m) => m.platform);
|
|
3660
3671
|
return [
|
|
3661
3672
|
`storage: ${provider}`,
|
|
3662
3673
|
`memory tools: ${memoryEnabled ? "enabled" : "disabled"}`,
|
|
3663
3674
|
`auth: ${authRequired ? "required" : "not required"}`,
|
|
3664
|
-
`telemetry: ${telemetryEnabled ? "enabled" : "disabled"}
|
|
3675
|
+
`telemetry: ${telemetryEnabled ? "enabled" : "disabled"}`,
|
|
3676
|
+
...messagingPlatforms.length > 0 ? [`messaging: ${messagingPlatforms.join(", ")}`] : []
|
|
3665
3677
|
];
|
|
3666
3678
|
};
|
|
3667
3679
|
var getOnboardingMarkerPath = async (workingDir) => {
|
|
@@ -3736,6 +3748,8 @@ var consumeFirstRunIntro = async (workingDir, input2) => {
|
|
|
3736
3748
|
"- **Enable auth**: Add bearer tokens or custom authentication",
|
|
3737
3749
|
"- **Turn on telemetry**: Track usage with OpenTelemetry/OTLP",
|
|
3738
3750
|
"- **Add MCP servers**: Connect external tool servers",
|
|
3751
|
+
"- **Schedule cron jobs**: Set up recurring tasks in AGENT.md frontmatter",
|
|
3752
|
+
"- **Connect to Slack**: Set up messaging so users can @mention this agent in Slack",
|
|
3739
3753
|
"",
|
|
3740
3754
|
"Just let me know what you'd like to work on!\n"
|
|
3741
3755
|
].join("\n");
|
|
@@ -4163,6 +4177,40 @@ ${name}/
|
|
|
4163
4177
|
\u2514\u2500\u2500 fetch-page.ts
|
|
4164
4178
|
\`\`\`
|
|
4165
4179
|
|
|
4180
|
+
## Cron Jobs
|
|
4181
|
+
|
|
4182
|
+
Define scheduled tasks in \`AGENT.md\` frontmatter:
|
|
4183
|
+
|
|
4184
|
+
\`\`\`yaml
|
|
4185
|
+
cron:
|
|
4186
|
+
daily-report:
|
|
4187
|
+
schedule: "0 9 * * *"
|
|
4188
|
+
task: "Generate the daily sales report"
|
|
4189
|
+
\`\`\`
|
|
4190
|
+
|
|
4191
|
+
- \`poncho dev\`: jobs run via an in-process scheduler.
|
|
4192
|
+
- \`poncho build vercel\`: generates \`vercel.json\` cron entries.
|
|
4193
|
+
- Docker/Fly.io: scheduler runs automatically.
|
|
4194
|
+
- Trigger manually: \`curl http://localhost:3000/api/cron/daily-report\`
|
|
4195
|
+
|
|
4196
|
+
## Messaging (Slack)
|
|
4197
|
+
|
|
4198
|
+
Connect your agent to Slack so it responds to @mentions:
|
|
4199
|
+
|
|
4200
|
+
1. Create a Slack App at [api.slack.com/apps](https://api.slack.com/apps)
|
|
4201
|
+
2. Add Bot Token Scopes: \`app_mentions:read\`, \`chat:write\`, \`reactions:write\`
|
|
4202
|
+
3. Enable Event Subscriptions, set Request URL to \`https://<your-url>/api/messaging/slack\`, subscribe to \`app_mention\`
|
|
4203
|
+
4. Install to workspace, copy Bot Token and Signing Secret
|
|
4204
|
+
5. Set env vars:
|
|
4205
|
+
\`\`\`
|
|
4206
|
+
SLACK_BOT_TOKEN=xoxb-...
|
|
4207
|
+
SLACK_SIGNING_SECRET=...
|
|
4208
|
+
\`\`\`
|
|
4209
|
+
6. Add to \`poncho.config.js\`:
|
|
4210
|
+
\`\`\`javascript
|
|
4211
|
+
messaging: [{ platform: 'slack' }]
|
|
4212
|
+
\`\`\`
|
|
4213
|
+
|
|
4166
4214
|
## Deployment
|
|
4167
4215
|
|
|
4168
4216
|
\`\`\`bash
|
|
@@ -4367,6 +4415,55 @@ var ensureRuntimeCliDependency = async (projectDir, cliVersion, config) => {
|
|
|
4367
4415
|
`, "utf8");
|
|
4368
4416
|
return { paths: [relative(projectDir, packageJsonPath)], addedDeps };
|
|
4369
4417
|
};
|
|
4418
|
+
var checkVercelCronDrift = async (projectDir) => {
|
|
4419
|
+
const vercelJsonPath = resolve3(projectDir, "vercel.json");
|
|
4420
|
+
try {
|
|
4421
|
+
await access2(vercelJsonPath);
|
|
4422
|
+
} catch {
|
|
4423
|
+
return;
|
|
4424
|
+
}
|
|
4425
|
+
let agentCrons = {};
|
|
4426
|
+
try {
|
|
4427
|
+
const agentMd = await readFile3(resolve3(projectDir, "AGENT.md"), "utf8");
|
|
4428
|
+
const parsed = parseAgentMarkdown(agentMd);
|
|
4429
|
+
agentCrons = parsed.frontmatter.cron ?? {};
|
|
4430
|
+
} catch {
|
|
4431
|
+
return;
|
|
4432
|
+
}
|
|
4433
|
+
let vercelCrons = [];
|
|
4434
|
+
try {
|
|
4435
|
+
const raw = await readFile3(vercelJsonPath, "utf8");
|
|
4436
|
+
const vercelConfig = JSON.parse(raw);
|
|
4437
|
+
vercelCrons = vercelConfig.crons ?? [];
|
|
4438
|
+
} catch {
|
|
4439
|
+
return;
|
|
4440
|
+
}
|
|
4441
|
+
const vercelCronMap = new Map(
|
|
4442
|
+
vercelCrons.filter((c) => c.path.startsWith("/api/cron/")).map((c) => [decodeURIComponent(c.path.replace("/api/cron/", "")), c.schedule])
|
|
4443
|
+
);
|
|
4444
|
+
const diffs = [];
|
|
4445
|
+
for (const [jobName, job] of Object.entries(agentCrons)) {
|
|
4446
|
+
const existing = vercelCronMap.get(jobName);
|
|
4447
|
+
if (!existing) {
|
|
4448
|
+
diffs.push(` + missing job "${jobName}" (${job.schedule})`);
|
|
4449
|
+
} else if (existing !== job.schedule) {
|
|
4450
|
+
diffs.push(` ~ "${jobName}" schedule changed: "${existing}" \u2192 "${job.schedule}"`);
|
|
4451
|
+
}
|
|
4452
|
+
vercelCronMap.delete(jobName);
|
|
4453
|
+
}
|
|
4454
|
+
for (const [jobName, schedule] of vercelCronMap) {
|
|
4455
|
+
diffs.push(` - removed job "${jobName}" (${schedule})`);
|
|
4456
|
+
}
|
|
4457
|
+
if (diffs.length > 0) {
|
|
4458
|
+
process.stderr.write(
|
|
4459
|
+
`\u26A0 vercel.json crons are out of sync with AGENT.md:
|
|
4460
|
+
${diffs.join("\n")}
|
|
4461
|
+
Run \`poncho build vercel --force\` to update.
|
|
4462
|
+
|
|
4463
|
+
`
|
|
4464
|
+
);
|
|
4465
|
+
}
|
|
4466
|
+
};
|
|
4370
4467
|
var scaffoldDeployTarget = async (projectDir, target, options) => {
|
|
4371
4468
|
const writtenPaths = [];
|
|
4372
4469
|
const cliVersion = await readCliVersion();
|
|
@@ -4401,21 +4498,35 @@ export default async function handler(req, res) {
|
|
|
4401
4498
|
{ force: options?.force, writtenPaths, baseDir: projectDir }
|
|
4402
4499
|
);
|
|
4403
4500
|
const vercelConfigPath = resolve3(projectDir, "vercel.json");
|
|
4501
|
+
let vercelCrons;
|
|
4502
|
+
try {
|
|
4503
|
+
const agentMd = await readFile3(resolve3(projectDir, "AGENT.md"), "utf8");
|
|
4504
|
+
const parsed = parseAgentMarkdown(agentMd);
|
|
4505
|
+
if (parsed.frontmatter.cron) {
|
|
4506
|
+
vercelCrons = Object.entries(parsed.frontmatter.cron).map(
|
|
4507
|
+
([jobName, job]) => ({
|
|
4508
|
+
path: `/api/cron/${encodeURIComponent(jobName)}`,
|
|
4509
|
+
schedule: job.schedule
|
|
4510
|
+
})
|
|
4511
|
+
);
|
|
4512
|
+
}
|
|
4513
|
+
} catch {
|
|
4514
|
+
}
|
|
4515
|
+
const vercelConfig = {
|
|
4516
|
+
version: 2,
|
|
4517
|
+
functions: {
|
|
4518
|
+
"api/index.mjs": {
|
|
4519
|
+
includeFiles: "{AGENT.md,poncho.config.js,skills/**,tests/**,node_modules/.pnpm/marked@*/node_modules/marked/lib/marked.umd.js}"
|
|
4520
|
+
}
|
|
4521
|
+
},
|
|
4522
|
+
routes: [{ src: "/(.*)", dest: "/api/index.mjs" }]
|
|
4523
|
+
};
|
|
4524
|
+
if (vercelCrons && vercelCrons.length > 0) {
|
|
4525
|
+
vercelConfig.crons = vercelCrons;
|
|
4526
|
+
}
|
|
4404
4527
|
await writeScaffoldFile(
|
|
4405
4528
|
vercelConfigPath,
|
|
4406
|
-
`${JSON.stringify(
|
|
4407
|
-
{
|
|
4408
|
-
version: 2,
|
|
4409
|
-
functions: {
|
|
4410
|
-
"api/index.mjs": {
|
|
4411
|
-
includeFiles: "{AGENT.md,poncho.config.js,skills/**,tests/**,node_modules/.pnpm/marked@*/node_modules/marked/lib/marked.umd.js}"
|
|
4412
|
-
}
|
|
4413
|
-
},
|
|
4414
|
-
routes: [{ src: "/(.*)", dest: "/api/index.mjs" }]
|
|
4415
|
-
},
|
|
4416
|
-
null,
|
|
4417
|
-
2
|
|
4418
|
-
)}
|
|
4529
|
+
`${JSON.stringify(vercelConfig, null, 2)}
|
|
4419
4530
|
`,
|
|
4420
4531
|
{ force: options?.force, writtenPaths, baseDir: projectDir }
|
|
4421
4532
|
);
|
|
@@ -4458,6 +4569,11 @@ export const handler = async (event = {}) => {
|
|
|
4458
4569
|
});
|
|
4459
4570
|
return { statusCode: 200, headers: { "content-type": "application/json" }, body };
|
|
4460
4571
|
};
|
|
4572
|
+
|
|
4573
|
+
// Cron jobs: use AWS EventBridge (CloudWatch Events) to trigger scheduled invocations.
|
|
4574
|
+
// Create a rule for each cron job defined in AGENT.md that sends a GET request to:
|
|
4575
|
+
// /api/cron/<jobName>
|
|
4576
|
+
// Include the Authorization header with your PONCHO_AUTH_TOKEN as a Bearer token.
|
|
4461
4577
|
`,
|
|
4462
4578
|
{ force: options?.force, writtenPaths, baseDir: projectDir }
|
|
4463
4579
|
);
|
|
@@ -4687,6 +4803,7 @@ var createRequestHandler = async (options) => {
|
|
|
4687
4803
|
let agentName = "Agent";
|
|
4688
4804
|
let agentModelProvider = "anthropic";
|
|
4689
4805
|
let agentModelName = "claude-opus-4-5";
|
|
4806
|
+
let cronJobs = {};
|
|
4690
4807
|
try {
|
|
4691
4808
|
const agentMd = await readFile3(resolve3(workingDir, "AGENT.md"), "utf8");
|
|
4692
4809
|
const nameMatch = agentMd.match(/^name:\s*(.+)$/m);
|
|
@@ -4701,6 +4818,11 @@ var createRequestHandler = async (options) => {
|
|
|
4701
4818
|
if (modelMatch?.[1]) {
|
|
4702
4819
|
agentModelName = modelMatch[1].trim().replace(/^["']|["']$/g, "");
|
|
4703
4820
|
}
|
|
4821
|
+
try {
|
|
4822
|
+
const parsed = parseAgentMarkdown(agentMd);
|
|
4823
|
+
cronJobs = parsed.frontmatter.cron ?? {};
|
|
4824
|
+
} catch {
|
|
4825
|
+
}
|
|
4704
4826
|
} catch {
|
|
4705
4827
|
}
|
|
4706
4828
|
const runOwners = /* @__PURE__ */ new Map();
|
|
@@ -4791,6 +4913,90 @@ var createRequestHandler = async (options) => {
|
|
|
4791
4913
|
workingDir,
|
|
4792
4914
|
agentId: identity.id
|
|
4793
4915
|
});
|
|
4916
|
+
const messagingRoutes = /* @__PURE__ */ new Map();
|
|
4917
|
+
const messagingRouteRegistrar = (method, path, routeHandler) => {
|
|
4918
|
+
let byMethod = messagingRoutes.get(path);
|
|
4919
|
+
if (!byMethod) {
|
|
4920
|
+
byMethod = /* @__PURE__ */ new Map();
|
|
4921
|
+
messagingRoutes.set(path, byMethod);
|
|
4922
|
+
}
|
|
4923
|
+
byMethod.set(method, routeHandler);
|
|
4924
|
+
};
|
|
4925
|
+
const messagingRunner = {
|
|
4926
|
+
async getOrCreateConversation(conversationId, meta) {
|
|
4927
|
+
const existing = await conversationStore.get(conversationId);
|
|
4928
|
+
if (existing) {
|
|
4929
|
+
return { messages: existing.messages };
|
|
4930
|
+
}
|
|
4931
|
+
const now = Date.now();
|
|
4932
|
+
const conversation = {
|
|
4933
|
+
conversationId,
|
|
4934
|
+
title: meta.title ?? `${meta.platform} thread`,
|
|
4935
|
+
messages: [],
|
|
4936
|
+
ownerId: meta.ownerId,
|
|
4937
|
+
tenantId: null,
|
|
4938
|
+
createdAt: now,
|
|
4939
|
+
updatedAt: now
|
|
4940
|
+
};
|
|
4941
|
+
await conversationStore.update(conversation);
|
|
4942
|
+
return { messages: [] };
|
|
4943
|
+
},
|
|
4944
|
+
async run(conversationId, input2) {
|
|
4945
|
+
const output = await harness.runToCompletion({
|
|
4946
|
+
task: input2.task,
|
|
4947
|
+
messages: input2.messages
|
|
4948
|
+
});
|
|
4949
|
+
const response = output.result.response ?? "";
|
|
4950
|
+
const conversation = await conversationStore.get(conversationId);
|
|
4951
|
+
if (conversation) {
|
|
4952
|
+
conversation.messages = [
|
|
4953
|
+
...input2.messages,
|
|
4954
|
+
{ role: "user", content: input2.task },
|
|
4955
|
+
{ role: "assistant", content: response }
|
|
4956
|
+
];
|
|
4957
|
+
await conversationStore.update(conversation);
|
|
4958
|
+
}
|
|
4959
|
+
return { response };
|
|
4960
|
+
}
|
|
4961
|
+
};
|
|
4962
|
+
const messagingBridges = [];
|
|
4963
|
+
if (config?.messaging && config.messaging.length > 0) {
|
|
4964
|
+
let waitUntilHook;
|
|
4965
|
+
if (process.env.VERCEL) {
|
|
4966
|
+
try {
|
|
4967
|
+
const modName = "@vercel/functions";
|
|
4968
|
+
const mod = await import(
|
|
4969
|
+
/* webpackIgnore: true */
|
|
4970
|
+
modName
|
|
4971
|
+
);
|
|
4972
|
+
waitUntilHook = mod.waitUntil;
|
|
4973
|
+
} catch {
|
|
4974
|
+
}
|
|
4975
|
+
}
|
|
4976
|
+
for (const channelConfig of config.messaging) {
|
|
4977
|
+
if (channelConfig.platform === "slack") {
|
|
4978
|
+
const adapter = new SlackAdapter({
|
|
4979
|
+
botTokenEnv: channelConfig.botTokenEnv,
|
|
4980
|
+
signingSecretEnv: channelConfig.signingSecretEnv
|
|
4981
|
+
});
|
|
4982
|
+
const bridge = new AgentBridge({
|
|
4983
|
+
adapter,
|
|
4984
|
+
runner: messagingRunner,
|
|
4985
|
+
waitUntil: waitUntilHook
|
|
4986
|
+
});
|
|
4987
|
+
adapter.registerRoutes(messagingRouteRegistrar);
|
|
4988
|
+
try {
|
|
4989
|
+
await bridge.start();
|
|
4990
|
+
messagingBridges.push(bridge);
|
|
4991
|
+
console.log(` Slack messaging enabled at /api/messaging/slack`);
|
|
4992
|
+
} catch (err) {
|
|
4993
|
+
console.warn(
|
|
4994
|
+
` Slack messaging disabled: ${err instanceof Error ? err.message : String(err)}`
|
|
4995
|
+
);
|
|
4996
|
+
}
|
|
4997
|
+
}
|
|
4998
|
+
}
|
|
4999
|
+
}
|
|
4794
5000
|
const sessionStore = new SessionStore();
|
|
4795
5001
|
const loginRateLimiter = new LoginRateLimiter();
|
|
4796
5002
|
const authToken = process.env.PONCHO_AUTH_TOKEN ?? "";
|
|
@@ -4811,7 +5017,7 @@ var createRequestHandler = async (options) => {
|
|
|
4811
5017
|
}
|
|
4812
5018
|
return verifyPassphrase(match[1], authToken);
|
|
4813
5019
|
};
|
|
4814
|
-
|
|
5020
|
+
const handler = async (request, response) => {
|
|
4815
5021
|
if (!request.url || !request.method) {
|
|
4816
5022
|
writeJson(response, 404, { error: "Not found" });
|
|
4817
5023
|
return;
|
|
@@ -4848,6 +5054,14 @@ var createRequestHandler = async (options) => {
|
|
|
4848
5054
|
writeJson(response, 200, { status: "ok" });
|
|
4849
5055
|
return;
|
|
4850
5056
|
}
|
|
5057
|
+
const messagingByMethod = messagingRoutes.get(pathname ?? "");
|
|
5058
|
+
if (messagingByMethod) {
|
|
5059
|
+
const routeHandler = messagingByMethod.get(request.method ?? "");
|
|
5060
|
+
if (routeHandler) {
|
|
5061
|
+
await routeHandler(request, response);
|
|
5062
|
+
return;
|
|
5063
|
+
}
|
|
5064
|
+
}
|
|
4851
5065
|
const cookies = parseCookies(request);
|
|
4852
5066
|
const sessionId = cookies.poncho_session;
|
|
4853
5067
|
const session = sessionId ? sessionStore.get(sessionId) : void 0;
|
|
@@ -5533,10 +5747,183 @@ var createRequestHandler = async (options) => {
|
|
|
5533
5747
|
}
|
|
5534
5748
|
return;
|
|
5535
5749
|
}
|
|
5750
|
+
const cronMatch = pathname.match(/^\/api\/cron\/([^/]+)$/);
|
|
5751
|
+
if (cronMatch && (request.method === "GET" || request.method === "POST")) {
|
|
5752
|
+
const jobName = decodeURIComponent(cronMatch[1] ?? "");
|
|
5753
|
+
const cronJob = cronJobs[jobName];
|
|
5754
|
+
if (!cronJob) {
|
|
5755
|
+
writeJson(response, 404, {
|
|
5756
|
+
code: "CRON_JOB_NOT_FOUND",
|
|
5757
|
+
message: `Cron job "${jobName}" is not defined in AGENT.md`
|
|
5758
|
+
});
|
|
5759
|
+
return;
|
|
5760
|
+
}
|
|
5761
|
+
const urlObj = new URL(request.url ?? "/", `http://${request.headers.host ?? "localhost"}`);
|
|
5762
|
+
const continueConversationId = urlObj.searchParams.get("continue");
|
|
5763
|
+
const continuationCount = Number(urlObj.searchParams.get("continuation") ?? "0");
|
|
5764
|
+
const maxContinuations = 5;
|
|
5765
|
+
if (continuationCount >= maxContinuations) {
|
|
5766
|
+
writeJson(response, 200, {
|
|
5767
|
+
conversationId: continueConversationId,
|
|
5768
|
+
status: "max_continuations_reached",
|
|
5769
|
+
continuations: continuationCount
|
|
5770
|
+
});
|
|
5771
|
+
return;
|
|
5772
|
+
}
|
|
5773
|
+
const cronOwnerId = ownerId;
|
|
5774
|
+
const start = Date.now();
|
|
5775
|
+
try {
|
|
5776
|
+
let conversation;
|
|
5777
|
+
let historyMessages = [];
|
|
5778
|
+
if (continueConversationId) {
|
|
5779
|
+
conversation = await conversationStore.get(continueConversationId);
|
|
5780
|
+
if (!conversation) {
|
|
5781
|
+
writeJson(response, 404, {
|
|
5782
|
+
code: "CONVERSATION_NOT_FOUND",
|
|
5783
|
+
message: "Continuation conversation not found"
|
|
5784
|
+
});
|
|
5785
|
+
return;
|
|
5786
|
+
}
|
|
5787
|
+
historyMessages = [...conversation.messages];
|
|
5788
|
+
} else {
|
|
5789
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
5790
|
+
conversation = await conversationStore.create(
|
|
5791
|
+
cronOwnerId,
|
|
5792
|
+
`[cron] ${jobName} ${timestamp}`
|
|
5793
|
+
);
|
|
5794
|
+
}
|
|
5795
|
+
const abortController = new AbortController();
|
|
5796
|
+
let assistantResponse = "";
|
|
5797
|
+
let latestRunId = "";
|
|
5798
|
+
const toolTimeline = [];
|
|
5799
|
+
const sections = [];
|
|
5800
|
+
let currentTools = [];
|
|
5801
|
+
let currentText = "";
|
|
5802
|
+
let runResult = {
|
|
5803
|
+
status: "completed",
|
|
5804
|
+
steps: 0
|
|
5805
|
+
};
|
|
5806
|
+
const platformMaxDurationSec = Number(process.env.PONCHO_MAX_DURATION) || 0;
|
|
5807
|
+
const softDeadlineMs = platformMaxDurationSec > 0 ? platformMaxDurationSec * 800 : 0;
|
|
5808
|
+
for await (const event of harness.runWithTelemetry({
|
|
5809
|
+
task: cronJob.task,
|
|
5810
|
+
parameters: { __activeConversationId: conversation.conversationId },
|
|
5811
|
+
messages: historyMessages,
|
|
5812
|
+
abortSignal: abortController.signal
|
|
5813
|
+
})) {
|
|
5814
|
+
if (event.type === "run:started") {
|
|
5815
|
+
latestRunId = event.runId;
|
|
5816
|
+
}
|
|
5817
|
+
if (event.type === "model:chunk") {
|
|
5818
|
+
if (currentTools.length > 0) {
|
|
5819
|
+
sections.push({ type: "tools", content: currentTools });
|
|
5820
|
+
currentTools = [];
|
|
5821
|
+
}
|
|
5822
|
+
assistantResponse += event.content;
|
|
5823
|
+
currentText += event.content;
|
|
5824
|
+
}
|
|
5825
|
+
if (event.type === "tool:started") {
|
|
5826
|
+
if (currentText.length > 0) {
|
|
5827
|
+
sections.push({ type: "text", content: currentText });
|
|
5828
|
+
currentText = "";
|
|
5829
|
+
}
|
|
5830
|
+
const toolText = `- start \`${event.tool}\``;
|
|
5831
|
+
toolTimeline.push(toolText);
|
|
5832
|
+
currentTools.push(toolText);
|
|
5833
|
+
}
|
|
5834
|
+
if (event.type === "tool:completed") {
|
|
5835
|
+
const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
|
|
5836
|
+
toolTimeline.push(toolText);
|
|
5837
|
+
currentTools.push(toolText);
|
|
5838
|
+
}
|
|
5839
|
+
if (event.type === "tool:error") {
|
|
5840
|
+
const toolText = `- error \`${event.tool}\`: ${event.error}`;
|
|
5841
|
+
toolTimeline.push(toolText);
|
|
5842
|
+
currentTools.push(toolText);
|
|
5843
|
+
}
|
|
5844
|
+
if (event.type === "run:completed") {
|
|
5845
|
+
runResult = {
|
|
5846
|
+
status: event.result.status,
|
|
5847
|
+
steps: event.result.steps,
|
|
5848
|
+
continuation: event.result.continuation
|
|
5849
|
+
};
|
|
5850
|
+
if (!assistantResponse && event.result.response) {
|
|
5851
|
+
assistantResponse = event.result.response;
|
|
5852
|
+
}
|
|
5853
|
+
}
|
|
5854
|
+
await telemetry.emit(event);
|
|
5855
|
+
}
|
|
5856
|
+
if (currentTools.length > 0) {
|
|
5857
|
+
sections.push({ type: "tools", content: currentTools });
|
|
5858
|
+
}
|
|
5859
|
+
if (currentText.length > 0) {
|
|
5860
|
+
sections.push({ type: "text", content: currentText });
|
|
5861
|
+
currentText = "";
|
|
5862
|
+
}
|
|
5863
|
+
const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0;
|
|
5864
|
+
const assistantMetadata = toolTimeline.length > 0 || sections.length > 0 ? {
|
|
5865
|
+
toolActivity: [...toolTimeline],
|
|
5866
|
+
sections: sections.length > 0 ? sections : void 0
|
|
5867
|
+
} : void 0;
|
|
5868
|
+
const messages = [
|
|
5869
|
+
...historyMessages,
|
|
5870
|
+
...continueConversationId ? [] : [{ role: "user", content: cronJob.task }],
|
|
5871
|
+
...hasContent ? [{ role: "assistant", content: assistantResponse, metadata: assistantMetadata }] : []
|
|
5872
|
+
];
|
|
5873
|
+
conversation.messages = messages;
|
|
5874
|
+
conversation.runtimeRunId = latestRunId || conversation.runtimeRunId;
|
|
5875
|
+
conversation.updatedAt = Date.now();
|
|
5876
|
+
await conversationStore.update(conversation);
|
|
5877
|
+
if (runResult.continuation && softDeadlineMs > 0) {
|
|
5878
|
+
const selfUrl = `http://${request.headers.host ?? "localhost"}${pathname}?continue=${encodeURIComponent(conversation.conversationId)}&continuation=${continuationCount + 1}`;
|
|
5879
|
+
try {
|
|
5880
|
+
const selfRes = await fetch(selfUrl, {
|
|
5881
|
+
method: "GET",
|
|
5882
|
+
headers: request.headers.authorization ? { authorization: request.headers.authorization } : {}
|
|
5883
|
+
});
|
|
5884
|
+
const selfBody = await selfRes.json();
|
|
5885
|
+
writeJson(response, 200, {
|
|
5886
|
+
conversationId: conversation.conversationId,
|
|
5887
|
+
status: "continued",
|
|
5888
|
+
continuations: continuationCount + 1,
|
|
5889
|
+
finalResult: selfBody,
|
|
5890
|
+
duration: Date.now() - start
|
|
5891
|
+
});
|
|
5892
|
+
} catch (continueError) {
|
|
5893
|
+
writeJson(response, 200, {
|
|
5894
|
+
conversationId: conversation.conversationId,
|
|
5895
|
+
status: "continuation_failed",
|
|
5896
|
+
error: continueError instanceof Error ? continueError.message : "Unknown error",
|
|
5897
|
+
duration: Date.now() - start,
|
|
5898
|
+
steps: runResult.steps
|
|
5899
|
+
});
|
|
5900
|
+
}
|
|
5901
|
+
return;
|
|
5902
|
+
}
|
|
5903
|
+
writeJson(response, 200, {
|
|
5904
|
+
conversationId: conversation.conversationId,
|
|
5905
|
+
status: runResult.status,
|
|
5906
|
+
response: assistantResponse.slice(0, 500),
|
|
5907
|
+
duration: Date.now() - start,
|
|
5908
|
+
steps: runResult.steps
|
|
5909
|
+
});
|
|
5910
|
+
} catch (error) {
|
|
5911
|
+
writeJson(response, 500, {
|
|
5912
|
+
code: "CRON_RUN_ERROR",
|
|
5913
|
+
message: error instanceof Error ? error.message : "Unknown error"
|
|
5914
|
+
});
|
|
5915
|
+
}
|
|
5916
|
+
return;
|
|
5917
|
+
}
|
|
5536
5918
|
writeJson(response, 404, { error: "Not found" });
|
|
5537
5919
|
};
|
|
5920
|
+
handler._harness = harness;
|
|
5921
|
+
handler._cronJobs = cronJobs;
|
|
5922
|
+
handler._conversationStore = conversationStore;
|
|
5923
|
+
return handler;
|
|
5538
5924
|
};
|
|
5539
5925
|
var startDevServer = async (port, options) => {
|
|
5926
|
+
const workingDir = options?.workingDir ?? process.cwd();
|
|
5540
5927
|
const handler = await createRequestHandler(options);
|
|
5541
5928
|
const server = createServer(handler);
|
|
5542
5929
|
const actualPort = await listenOnAvailablePort(server, port);
|
|
@@ -5546,7 +5933,141 @@ var startDevServer = async (port, options) => {
|
|
|
5546
5933
|
}
|
|
5547
5934
|
process.stdout.write(`Poncho dev server running at http://localhost:${actualPort}
|
|
5548
5935
|
`);
|
|
5936
|
+
await checkVercelCronDrift(workingDir);
|
|
5937
|
+
const { Cron } = await import("croner");
|
|
5938
|
+
let activeJobs = [];
|
|
5939
|
+
const scheduleCronJobs = (jobs) => {
|
|
5940
|
+
for (const job of activeJobs) {
|
|
5941
|
+
job.stop();
|
|
5942
|
+
}
|
|
5943
|
+
activeJobs = [];
|
|
5944
|
+
const entries = Object.entries(jobs);
|
|
5945
|
+
if (entries.length === 0) return;
|
|
5946
|
+
const harness = handler._harness;
|
|
5947
|
+
const store = handler._conversationStore;
|
|
5948
|
+
if (!harness || !store) return;
|
|
5949
|
+
for (const [jobName, config] of entries) {
|
|
5950
|
+
const job = new Cron(
|
|
5951
|
+
config.schedule,
|
|
5952
|
+
{ timezone: config.timezone ?? "UTC" },
|
|
5953
|
+
async () => {
|
|
5954
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
5955
|
+
process.stdout.write(`[cron] ${jobName} started at ${timestamp}
|
|
5956
|
+
`);
|
|
5957
|
+
const start = Date.now();
|
|
5958
|
+
try {
|
|
5959
|
+
const conversation = await store.create(
|
|
5960
|
+
"local-owner",
|
|
5961
|
+
`[cron] ${jobName} ${timestamp}`
|
|
5962
|
+
);
|
|
5963
|
+
let assistantResponse = "";
|
|
5964
|
+
let steps = 0;
|
|
5965
|
+
const toolTimeline = [];
|
|
5966
|
+
const sections = [];
|
|
5967
|
+
let currentTools = [];
|
|
5968
|
+
let currentText = "";
|
|
5969
|
+
for await (const event of harness.runWithTelemetry({
|
|
5970
|
+
task: config.task,
|
|
5971
|
+
parameters: { __activeConversationId: conversation.conversationId },
|
|
5972
|
+
messages: []
|
|
5973
|
+
})) {
|
|
5974
|
+
if (event.type === "model:chunk") {
|
|
5975
|
+
if (currentTools.length > 0) {
|
|
5976
|
+
sections.push({ type: "tools", content: currentTools });
|
|
5977
|
+
currentTools = [];
|
|
5978
|
+
}
|
|
5979
|
+
assistantResponse += event.content;
|
|
5980
|
+
currentText += event.content;
|
|
5981
|
+
}
|
|
5982
|
+
if (event.type === "tool:started") {
|
|
5983
|
+
if (currentText.length > 0) {
|
|
5984
|
+
sections.push({ type: "text", content: currentText });
|
|
5985
|
+
currentText = "";
|
|
5986
|
+
}
|
|
5987
|
+
const toolText = `- start \`${event.tool}\``;
|
|
5988
|
+
toolTimeline.push(toolText);
|
|
5989
|
+
currentTools.push(toolText);
|
|
5990
|
+
}
|
|
5991
|
+
if (event.type === "tool:completed") {
|
|
5992
|
+
const toolText = `- done \`${event.tool}\` (${event.duration}ms)`;
|
|
5993
|
+
toolTimeline.push(toolText);
|
|
5994
|
+
currentTools.push(toolText);
|
|
5995
|
+
}
|
|
5996
|
+
if (event.type === "tool:error") {
|
|
5997
|
+
const toolText = `- error \`${event.tool}\`: ${event.error}`;
|
|
5998
|
+
toolTimeline.push(toolText);
|
|
5999
|
+
currentTools.push(toolText);
|
|
6000
|
+
}
|
|
6001
|
+
if (event.type === "run:completed") {
|
|
6002
|
+
steps = event.result.steps;
|
|
6003
|
+
if (!assistantResponse && event.result.response) {
|
|
6004
|
+
assistantResponse = event.result.response;
|
|
6005
|
+
}
|
|
6006
|
+
}
|
|
6007
|
+
}
|
|
6008
|
+
if (currentTools.length > 0) {
|
|
6009
|
+
sections.push({ type: "tools", content: currentTools });
|
|
6010
|
+
}
|
|
6011
|
+
if (currentText.length > 0) {
|
|
6012
|
+
sections.push({ type: "text", content: currentText });
|
|
6013
|
+
}
|
|
6014
|
+
const hasContent = assistantResponse.length > 0 || toolTimeline.length > 0;
|
|
6015
|
+
const assistantMetadata = toolTimeline.length > 0 || sections.length > 0 ? {
|
|
6016
|
+
toolActivity: [...toolTimeline],
|
|
6017
|
+
sections: sections.length > 0 ? sections : void 0
|
|
6018
|
+
} : void 0;
|
|
6019
|
+
conversation.messages = [
|
|
6020
|
+
{ role: "user", content: config.task },
|
|
6021
|
+
...hasContent ? [{ role: "assistant", content: assistantResponse, metadata: assistantMetadata }] : []
|
|
6022
|
+
];
|
|
6023
|
+
conversation.updatedAt = Date.now();
|
|
6024
|
+
await store.update(conversation);
|
|
6025
|
+
const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
|
|
6026
|
+
process.stdout.write(
|
|
6027
|
+
`[cron] ${jobName} completed in ${elapsed}s (${steps} steps)
|
|
6028
|
+
`
|
|
6029
|
+
);
|
|
6030
|
+
} catch (error) {
|
|
6031
|
+
const elapsed = ((Date.now() - start) / 1e3).toFixed(1);
|
|
6032
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
6033
|
+
process.stderr.write(
|
|
6034
|
+
`[cron] ${jobName} failed after ${elapsed}s: ${msg}
|
|
6035
|
+
`
|
|
6036
|
+
);
|
|
6037
|
+
}
|
|
6038
|
+
}
|
|
6039
|
+
);
|
|
6040
|
+
activeJobs.push(job);
|
|
6041
|
+
}
|
|
6042
|
+
process.stdout.write(
|
|
6043
|
+
`[cron] Scheduled ${entries.length} job${entries.length === 1 ? "" : "s"}: ${entries.map(([n]) => n).join(", ")}
|
|
6044
|
+
`
|
|
6045
|
+
);
|
|
6046
|
+
};
|
|
6047
|
+
const initialCronJobs = handler._cronJobs ?? {};
|
|
6048
|
+
scheduleCronJobs(initialCronJobs);
|
|
6049
|
+
const agentMdPath = resolve3(workingDir, "AGENT.md");
|
|
6050
|
+
let reloadDebounce = null;
|
|
6051
|
+
const watcher = fsWatch(agentMdPath, () => {
|
|
6052
|
+
if (reloadDebounce) clearTimeout(reloadDebounce);
|
|
6053
|
+
reloadDebounce = setTimeout(async () => {
|
|
6054
|
+
try {
|
|
6055
|
+
const agentMd = await readFile3(agentMdPath, "utf8");
|
|
6056
|
+
const parsed = parseAgentMarkdown(agentMd);
|
|
6057
|
+
const newJobs = parsed.frontmatter.cron ?? {};
|
|
6058
|
+
handler._cronJobs = newJobs;
|
|
6059
|
+
scheduleCronJobs(newJobs);
|
|
6060
|
+
process.stdout.write(`[cron] Reloaded: ${Object.keys(newJobs).length} jobs scheduled
|
|
6061
|
+
`);
|
|
6062
|
+
} catch {
|
|
6063
|
+
}
|
|
6064
|
+
}, 500);
|
|
6065
|
+
});
|
|
5549
6066
|
const shutdown = () => {
|
|
6067
|
+
watcher.close();
|
|
6068
|
+
for (const job of activeJobs) {
|
|
6069
|
+
job.stop();
|
|
6070
|
+
}
|
|
5550
6071
|
server.close();
|
|
5551
6072
|
server.closeAllConnections?.();
|
|
5552
6073
|
process.exit(0);
|
|
@@ -5637,7 +6158,7 @@ var runInteractive = async (workingDir, params) => {
|
|
|
5637
6158
|
await harness.initialize();
|
|
5638
6159
|
const identity = await ensureAgentIdentity2(workingDir);
|
|
5639
6160
|
try {
|
|
5640
|
-
const { runInteractiveInk } = await import("./run-interactive-ink-
|
|
6161
|
+
const { runInteractiveInk } = await import("./run-interactive-ink-VZBOYJYS.js");
|
|
5641
6162
|
await runInteractiveInk({
|
|
5642
6163
|
harness,
|
|
5643
6164
|
params,
|
|
@@ -5998,6 +6519,9 @@ Test summary: ${passed} passed, ${failed} failed
|
|
|
5998
6519
|
};
|
|
5999
6520
|
var buildTarget = async (workingDir, target, options) => {
|
|
6000
6521
|
const normalizedTarget = normalizeDeployTarget2(target);
|
|
6522
|
+
if (normalizedTarget === "vercel" && !options?.force) {
|
|
6523
|
+
await checkVercelCronDrift(workingDir);
|
|
6524
|
+
}
|
|
6001
6525
|
const writtenPaths = await scaffoldDeployTarget(workingDir, normalizedTarget, {
|
|
6002
6526
|
force: options?.force
|
|
6003
6527
|
});
|