Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,5 @@ if (!satisfies(process.version, NODE_VERSION_RANGE)) {
}

// Loaded dynamically so no dependency is evaluated before the check above.
// eslint-disable-next-line no-restricted-syntax -- main.ts is bin.ts's own layer.
await import('./main');
49 changes: 20 additions & 29 deletions docs/examples/run-agent-quack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,17 @@
// Needs local PostHog on :8010 (with its ai-gateway) and context-mill on :8765.
// POSTHOG_PERSONAL_API_KEY logs in. WIZARD_CI_GATEWAY_TOKEN_FILE holds the gateway token.
// QUACK_INSTALL_DIR sets the project the agent runs in (default: the current directory).
import {
configureGatewayFromCIEnvironment,
runAgent,
RunOutcome,
} from '@agent';
import { runAgent, RunOutcome } from '@agent';
import type { RunConfig, RunInput } from '@agent/types';
import {
Harness,
HAIKU_MODEL,
Sequence,
getSkillsBaseUrl,
} from '@shared/constants';
import { resolveApiKeyProject } from '@shared/api-key-login';
import { readCiGatewayCredential } from '@shared/ci-gateway';
import { initLocalDev, POSTHOG_LOCAL_URL } from '@shared/local-dev';
import { getOrAskForProjectData } from '@tui/auth/project-data';

// Point PostHog, skills and MCP at the local stack, like --local-posthog --local-context-mill --local-mcp.
initLocalDev({ localPosthog: true, localContextMill: true, localMcp: true });
Expand All @@ -28,16 +25,11 @@ initLocalDev({ localPosthog: true, localContextMill: true, localMcp: true });
const apiKey = process.env.POSTHOG_PERSONAL_API_KEY;
if (!apiKey) throw new Error('Set POSTHOG_PERSONAL_API_KEY');
const programId = 'posthog-integration'; // a program the local gateway admits
const login = await getOrAskForProjectData({
signup: false,
ci: true, // with apiKey, this skips OAuth
apiKey,
const { host, project, apiUser } = await resolveApiKeyProject(apiKey, {
baseUrl: POSTHOG_LOCAL_URL,
localMcp: true,
programId,
onWarning: (message) => console.warn(message),
});
// Use the token in WIZARD_CI_GATEWAY_TOKEN_FILE at WIZARD_CI_GATEWAY_URL instead of minting one.
configureGatewayFromCIEnvironment(login.projectId, 'us');

// What the agent runs: one prompt, a small model, no Write, Edit or Bash.
const config: RunConfig = {
Expand All @@ -54,34 +46,33 @@ const config: RunConfig = {
docsUrl: 'https://posthog.com/docs',
},
composed: true, // a sub-run: no terminal outro
// runAgent doesn't resolve a route. Linear on the Anthropic harness keeps the transcript.
binding: {
sequence: Sequence.linear,
harness: Harness.anthropic,
model: HAIKU_MODEL,
// Linear on the Anthropic harness keeps the transcript. With no flags, the agent runs this binding as is.
routing: {
binding: {
sequence: Sequence.linear,
harness: Harness.anthropic,
model: HAIKU_MODEL,
},
},
switchboard: { program: programId, composed: true, flags: {} },
skillsBaseUrl: getSkillsBaseUrl(),
wizardFlags: {},
wizardFlagPayloads: {},
wizardMetadata: {},
disallowedTools: ['Write', 'Edit', 'Bash'],
};

// Where and as whom: the project, the login and the flags.
const input: RunInput = {
installDir: process.env.QUACK_INSTALL_DIR ?? process.cwd(),
// Use the token in WIZARD_CI_GATEWAY_TOKEN_FILE at WIZARD_CI_GATEWAY_URL instead of minting one.
credentials: {
accessToken: login.accessToken,
refreshToken: login.refreshToken,
expiresAt: login.expiresAt,
projectApiKey: login.projectApiKey,
host: login.host,
projectId: login.projectId,
missingScopes: login.missingScopes,
accessToken: apiKey,
projectApiKey: project.api_token,
host,
projectId: project.id,
gateway: readCiGatewayCredential('us'),
},
project: login.project,
apiUser: login.user,
project,
apiUser,
flags: {
ci: false,
signup: false,
Expand Down
95 changes: 48 additions & 47 deletions docs/examples/run-program-quack.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@
// Needs local PostHog on :8010 (with its ai-gateway) and context-mill on :8765.
// POSTHOG_PERSONAL_API_KEY logs in. WIZARD_CI_GATEWAY_TOKEN_FILE holds the gateway token.
// QUACK_INSTALL_DIR sets the project the agent runs in (default: the current directory).
import { configureGatewayFromCIEnvironment, RunOutcome } from '@agent';
import { runProgram } from '@programs';
import {
buildSession,
resolveApiKeyLogin,
RunOutcome,
runProgram,
SessionStore,
} from '@programs';
import type { ProgramProgress } from '@programs/types';
import { Harness, HAIKU_MODEL, Sequence } from '@shared/constants';
import { readCiGatewayCredential } from '@shared/ci-gateway';
import { initLocalDev, POSTHOG_LOCAL_URL } from '@shared/local-dev';
import { getOrAskForProjectData } from '@tui/auth/project-data';

// Point PostHog, skills and MCP at the local stack, like --local-posthog --local-context-mill --local-mcp.
initLocalDev({ localPosthog: true, localContextMill: true, localMcp: true });
Expand All @@ -20,76 +25,72 @@ initLocalDev({ localPosthog: true, localContextMill: true, localMcp: true });
const apiKey = process.env.POSTHOG_PERSONAL_API_KEY;
if (!apiKey) throw new Error('Set POSTHOG_PERSONAL_API_KEY');
const programId = 'posthog-integration'; // a program the local gateway admits
const login = await getOrAskForProjectData({
signup: false,
ci: true, // with apiKey, this skips OAuth
apiKey,
const login = await resolveApiKeyLogin(apiKey, {
baseUrl: POSTHOG_LOCAL_URL,
localMcp: true,
programId,
onWarning: (message) => console.warn(message),
});
// Use the token in WIZARD_CI_GATEWAY_TOKEN_FILE at WIZARD_CI_GATEWAY_URL instead of minting one.
configureGatewayFromCIEnvironment(login.projectId, 'us');
login.posthog.gateway = readCiGatewayCredential('us');

// Log status lines as the program reports them. runProgram never waits for this.
function logProgress(progress: ProgramProgress): void {
if (progress.kind === 'program') return; // a data snapshot; it holds tokens, don't log it
const { event } = progress;
if (event.kind === 'status') console.log(`status: ${event.message}`);
if (event.kind === 'lifecycle') console.log(`lifecycle: ${event.phase}`);
}

// You own the session store: runProgram reads the launch values from it and writes the run into it.
const store = new SessionStore(
buildSession({
installDir: process.env.QUACK_INSTALL_DIR ?? process.cwd(),
baseUrl: POSTHOG_LOCAL_URL,
localMcp: true,
// A small model on the linear Anthropic route, where the transcript is kept.
sequence: Sequence.linear,
harness: Harness.anthropic,
model: HAIKU_MODEL,
}),
);
// The quack prompt reads nothing from the project, so skip the program's detection.
store.setDetectionComplete();

const result = await runProgram(
programId,
{
installDir: process.env.QUACK_INSTALL_DIR ?? process.cwd(),
// A caller-built run in place of the program's own: one prompt, and keep the reply.
run: {
integrationLabel: 'quack',
prompt: () => 'Reply with the single word quack. Use no tools.',
collectTranscript: true, // keep the agent's output for the reply below
requestRemark: false, // no closing remark
spinnerMessage: 'Quacking...',
successMessage: 'Quacked',
estimatedDurationMinutes: 1,
reportFile: '',
docsUrl: 'https://posthog.com/docs',
},
program: { disallowedTools: ['Write', 'Edit', 'Bash'] },
// The login from above, so runProgram skips its own login step.
credentials: {
posthog: {
accessToken: login.accessToken,
refreshToken: login.refreshToken,
expiresAt: login.expiresAt,
projectApiKey: login.projectApiKey,
host: login.host,
projectId: login.projectId,
missingScopes: login.missingScopes,
store,
// Laid over the program's own config: one prompt, keep the reply, no health check.
config: {
run: {
integrationLabel: 'quack',
prompt: () => 'Reply with the single word quack. Use no tools.',
collectTranscript: true, // keep the agent's output for the reply below
requestRemark: false, // no closing remark
spinnerMessage: 'Quacking...',
successMessage: 'Quacked',
estimatedDurationMinutes: 1,
reportFile: '',
docsUrl: 'https://posthog.com/docs',
},
project: login.project,
apiUser: login.user,
disallowedTools: ['Write', 'Edit', 'Bash'],
healthCheck: false,
},
credentials: login, // the login from above, so runProgram skips its own login step
composed: true, // a sub-run: no terminal outro
// A small model on the linear Anthropic route, where the transcript is kept.
overrides: {
sequence: Sequence.linear,
harness: Harness.anthropic,
model: HAIKU_MODEL,
},
flags: { localMcp: true },
host: { baseUrl: POSTHOG_LOCAL_URL },
wizardFlags: {}, // no flag snapshot to load
},
{
// You approved AI data processing for this local test user.
awaitAiApproval: () => Promise.resolve(true),
workflow: {
confirmStep: (step) =>
Promise.resolve(step.kind === 'ai-approval' || step.kind === 'run'),
},
onProgress: logProgress,
},
);

// Endings resolve to an outcome. The agent's reply is in its settled run's transcript.
const reply = result.settledRuns[0]?.result.snapshot.transcriptTail ?? '';
// Endings resolve to an outcome. The agent's reply is in its run's transcript.
const reply = result.runResults[0]?.snapshot.transcriptTail ?? '';
console.log(`reply: ${reply}`);
console.log(`outcome: ${result.outcome}`);
if (result.failure) console.log(`failure: ${result.failure.message}`);
Expand Down
Loading
Loading