Skip to content
Open
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
13 changes: 13 additions & 0 deletions src/cli/commands/deploy/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,19 @@ export async function handleDeploy(options: ValidatedDeployOptions): Promise<Dep

const postDeployWarnings: string[] = [];

// Auto-payment is a money-movement default; surface its posture per manager
// so an unattended-spend configuration is never silent at deploy time.
for (const manager of context.projectSpec.payments ?? []) {
if (manager.autoPayment !== false) {
const limit = manager.defaultSpendLimit ?? '10.00';
postDeployWarnings.push(
`Payment manager "${manager.name}": auto-payment is ENABLED — the agent will settle ` +
`402 responses automatically up to the per-session spend limit ($${limit}) with no ` +
`human approval. Set --auto-payment false on the manager to require manual approval.`
);
}
}

// Post-deploy: Enable online eval configs that have enableOnCreate (CFN deploys them as DISABLED).
// Only enable configs that are newly deployed — skip configs that already existed before this
// deploy run, so we don't re-enable configs a customer intentionally disabled.
Expand Down
21 changes: 19 additions & 2 deletions src/cli/primitives/PaymentManagerPrimitive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
PaymentManagerSchema,
} from '../../schema';
import type { RemoveResult } from '../commands/remove/types';
import { ANSI } from '../constants';
import { getErrorMessage } from '../errors';
import type { RemovalPreview, SchemaChange } from '../operations/remove/types';
import { getTemplatePath } from '../templates/templateRoot';
Expand Down Expand Up @@ -128,7 +129,7 @@ export class PaymentManagerPrimitive extends BasePrimitive<AddPaymentManagerOpti

async add(
options: AddPaymentManagerOptions
): Promise<AddResult<{ managerName: string; skippedRuntimes?: string[] }>> {
): Promise<AddResult<{ managerName: string; skippedRuntimes?: string[]; autoPaymentWarning?: string }>> {
try {
const project = await this.readProjectSpec();
// payments is optional in the schema (absent on projects with no payment
Expand Down Expand Up @@ -189,7 +190,20 @@ export class PaymentManagerPrimitive extends BasePrimitive<AddPaymentManagerOpti
}
}

return { success: true, managerName: options.name, skippedRuntimes };
// Auto-payment lets the agent settle 402 responses with no human in the
// loop, so surface a warning when it is active. Returned (not printed)
// because add() is shared by the CLI and the Ink TUI flow — each caller
// renders it through its own channel rather than writing to stderr
// mid-render.
const effectiveAutoPayment = options.autoPayment ?? DEFAULT_AUTO_PAYMENT;
const autoPaymentWarning = effectiveAutoPayment
? `auto-payment is ENABLED for manager "${options.name}". Agents will automatically settle ` +
`402 Payment Required responses up to the per-session spend limit ` +
`($${options.defaultSpendLimit ?? DEFAULT_SPEND_LIMIT}) with no human approval. ` +
`Re-run with --auto-payment false to require manual approval.`
: undefined;

return { success: true, managerName: options.name, skippedRuntimes, autoPaymentWarning };
} catch (err) {
return { success: false, error: toError(err) };
}
Expand Down Expand Up @@ -445,6 +459,9 @@ export class PaymentManagerPrimitive extends BasePrimitive<AddPaymentManagerOpti
console.log(JSON.stringify(serializeResult(result)));
} else if (result.success) {
console.log(`Added payment manager '${result.managerName}'`);
if (result.autoPaymentWarning) {
console.warn(`${ANSI.yellow}Warning: ${result.autoPaymentWarning}${ANSI.reset}`);
}
if (result.skippedRuntimes && result.skippedRuntimes.length > 0) {
console.warn(
`\nWarning: payment capability auto-wiring skipped for non-Strands runtime(s): ${result.skippedRuntimes.join(', ')}.`
Expand Down
21 changes: 21 additions & 0 deletions src/cli/primitives/__tests__/PaymentManagerPrimitive.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,27 @@ describe('PaymentManagerPrimitive', () => {
expect(result.error.message).toBe('disk read failure');
}
});

it('returns an auto-payment warning when enabled (default)', async () => {
mockReadProjectSpec.mockResolvedValue(makeProject({ runtimes: [] }));

const result = await primitive.add({ name: 'mgr1', authorizerType: 'AWS_IAM' });

expect(result.success).toBe(true);
if (!result.success) throw new Error('expected success');
expect(result.autoPaymentWarning).toMatch(/auto-payment is enabled/i);
expect(result.autoPaymentWarning).toContain('--auto-payment false');
});

it('returns no auto-payment warning when explicitly disabled', async () => {
mockReadProjectSpec.mockResolvedValue(makeProject({ runtimes: [] }));

const result = await primitive.add({ name: 'mgr2', authorizerType: 'AWS_IAM', autoPayment: false });

expect(result.success).toBe(true);
if (!result.success) throw new Error('expected success');
expect(result.autoPaymentWarning).toBeUndefined();
});
});

describe('remove()', () => {
Expand Down
16 changes: 13 additions & 3 deletions src/cli/tui/screens/payment/AddPaymentFlow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -348,9 +348,19 @@ export function AddPaymentFlow({
]
: [];

const warningFields = !flow.connectorConfig
? [{ label: '⚠ Warning', value: 'No connector — deploy will fail until you add one' }]
: [];
const warningFields = [
...(flow.managerConfig.autoPayment
? [
{
label: '⚠ Warning',
value: `Auto-payment ENABLED — agent settles 402s automatically up to $${flow.managerConfig.defaultSpendLimit}/session with no human approval`,
},
]
: []),
...(!flow.connectorConfig
? [{ label: '⚠ Warning', value: 'No connector — deploy will fail until you add one' }]
: []),
];

const allFields = [...managerFields, ...connectorFields, ...warningFields];

Expand Down
Loading