All notable changes to this module are recorded here. The format loosely
follows Keep a Changelog. Versions follow
SemVer; until the first 1.0.0 release,
command-level breaking changes are acceptable within 0.x versions.
(nothing yet)
Breaking change. aiflow create --no-launch is now FORBIDDEN.
In 0.3.3 the --no-launch path was discouraged (yellow WARNING +
creationComplete: false in the summary) but still allowed —
agents and scripts kept reaching for it and ending up with orphan
DRAFTs (workflow row exists but with no sequenceId on the engage
service, no bound mailbox, no time template, no meetingRouteId; the
scheduler cannot send anything against such a flow). 0.4.0 closes the
hole entirely: the --no-launch flag is rejected at the entry of
aiflow create with AIFLOW_NO_LAUNCH_FORBIDDEN (exit code 1), BEFORE
any side-effect call (no CSV upload, no create/list, no pitch fetch,
nothing). The flag-shape stays registered with Click only so the error
is helpful instead of Click's generic "no such option".
The error message and updated docs both point at the canonical replacement workflow:
-
Goal: create a flow but don't send right now —
aiflow create ...(always launches) thenaiflow pause FLOW_IDto flip ACTIVE → PAUSED. The flow is fully built (sequenceId, mailbox, time template all bound) and the scheduler is held off. This is the state--no-launchwas trying to produce, but without leaving an unbuilt orphan. -
Goal: edit prompts / settings before sending —
aiflow create ...→aiflow pause FLOW_ID→aiflow prompt-update FLOW_ID --file .../aiflow settings-update FLOW_ID ...→aiflow resume FLOW_ID(PAUSED → ACTIVE).
--launch/--no-launchClick option help text rewritten:--launchis documented as a no-op (default behaviour);--no-launchis documented as FORBIDDEN with the migration recipe inline.SKILL.mdaiflow createdescription leads with "--no-launchis FORBIDDEN" and the create → pause migration. The args list drops--launch/--no-launch. Examples drop--launch(now redundant) and add anaiflow create ... && aiflow pause $FLOW_IDexample for the build-but-don't-send case.README.mdoptions table flags--launchas a no-op and--no-launchas FORBIDDEN withAIFLOW_NO_LAUNCH_FORBIDDEN. Pipeline section drops the "(when--launch)" qualifier on step 8 — launch is always mandatory now.
test_no_launch_marks_creation_incomplete_with_next_step(the 0.3.3 happy-path test for--no-launch) renamed totest_explicit_no_launch_is_rejected. New assertions: exit code != 0; output containsAIFLOW_NO_LAUNCH_FORBIDDEN; none of the upload / create / save endpoints were called (the guard fires before any network hit); migration message contains all ofaiflow pause,aiflow prompt-update,aiflow settings-update,aiflow resume.test_default_create_triggers_regenerate_emailsno longer relies on--no-launchto short-circuit the launch path — it now mocks the full M3 set (get_setting/list_time_templates/list_personal_meetings/save_setting) so the wizard runs end to end.- 88 passing.
The interactive wizard's Launch this AIFlow now? (No = save as draft) confirm prompt is untouched in 0.4.0. It was authored before
the milestone-based DoD was enforced, and answering "No" still ends
up in the same orphan-DRAFT state --no-launch produced. This is
known and tracked separately — the focus of 0.4.0 is sealing the
flag-driven path that automation / agents take.
Definition-of-done is now enforced and surfaced. aiflow create is
documented and instrumented as THREE required milestones: M1 — file
upload + create/list (yields listId + flowId); M2 — pitch parsing
(test/connection → save/pitch) + email prompt templates persisted
(get/email/prompt → save/prompt); M3 — pick mailbox + time template
- meetingRouteId +
POST /save/setting(DRAFT → ACTIVE). Missing any milestone means the AIFlow is NOT successfully created.
_emit_creation_failure_bannerin the create wizard. On any pipeline failure (network error, validation error, user abort) the CLI now prints a prominent redAIFLOW NOT CREATEDcallout listing the failed milestone, the M1/M2/M3 checklist with [x]/[ ] markers, the partial state (listId / flowId), and concrete recovery commands (aiflow settings-update <id>to resume,aiflow delete <id>to discard the orphan DRAFT). Previously, mid-pipeline failures only produced a single redError: ...line, leaving users to figure out whether a workflow row had been created and what state it was in.creationComplete,completedMilestones,missing, andnextStepfields in theaiflow createsummary JSON.creationCompleteis the authoritative success signal (true only when M3-settings enteredcompletedMilestones); downstream agents and scripts should branch on it rather than onflowIdexistence.- JSON-mode addendum: a structured
{creationComplete, failedMilestone, completedMilestones, partialState, recovery}block is emitted on failure so agents can react without parsing the human banner.
--no-launchexit messaging promoted from a plainFlow left as DRAFTline to a yellowWARNING:callout that explicitly states the workflow has no sequenceId, no bound mailbox, no time template, and the scheduler cannot send emails until settings-update fires.SKILL.mdaiflow createdescription rewritten around the three-milestone definition of done. Agents are explicitly told to readcreationComplete+completedMilestones, not to assumeflowIdalone means success.
- Added
test_pipeline_failure_emits_aiflow_not_created_banner— M1 succeeds, M2 aborts attest/connection; asserts banner text, the M1=[x] / M2=[ ] / M3=[ ] checklist, the orphan flowId surfaces in theaiflow deleterecovery hint, andfailedMilestoneJSON field is correct. - Added
test_pipeline_failure_at_m3_save_setting_emits_banner— M1+M2 succeed, M3 fails atsave_setting; asserts the banner reflects the M3 failure even though everything upstream worked. - Extended
test_default_create_triggers_save_settingto also assertcreationComplete: true,missing: [],nextStep: nullon the happy path. - Added
test_no_launch_marks_creation_incomplete_with_next_step— the deliberate--no-launchskip path must reportcreationComplete: false+ a non-emptymissingarray + a recovery hint pointing ataiflow settings-update. - 88 passing (was 85).
test_website_connection(POST /api/v1/ai/workflow/test/connection) no longer hard-codes a 20s read timeout. It now uses the sharedself.timeout(default 300s, configurable via the existingFLASHREV_AIFLOW_TIMEOUTenv var). The previous 20s cap was hit whenever the upstream had to do a cold-cache fetch of the target site (e.g.tongchengir.com,ctrip.com— both routinely take 20-30s before the LLM pass even starts), causingaiflow createto abort client-side after the workflow row was created but before pitch / prompts / save-setting could run. Confirmed against the gateway in test env: acurlto/test/connectionfor the same URL returns in 21s, well within the new 5min ceiling.
test_test_website_connection_uses_20s_timeoutrenamed totest_test_website_connection_uses_shared_timeout. The assertion now pins the timeout toself.client.timeoutand adds a>= 180floor so future regressions that drop the default below 3min are caught (85 passing).
aiflow create --no-wizardnow defaults to launching the flow (launch_now=True), so/api/v1/ai/workflow/save/settingis fired as part of every create. Previously the--no-wizardbranch defaulted tolaunch_now=False, which produced flows without a sequence binding on the engage service, no mailbox bound, no time template, and no way for the scheduler to actually send emails — even though every other step in the create pipeline (pitch / prompts / step seed / regenerate) ran normally. Mirrors the frontend's "Launch AIFlow" button being the natural endpoint of the create wizard. Pass--no-launchexplicitly when you intend to populate prompts later viaaiflow prompt-updatebefore launching.
- Added
test_default_create_triggers_save_settingpinning the new default-on launch behaviour for the headless--no-wizardpath (85 passing, was 84).
Major rewrite to match the real search-website frontend flow: pitch
content is now LLM-generated from a URL, email prompt templates are
always populated before launch, and four post-create edit commands let
operators iterate on a flow without recreating it from scratch.
--url URL/--language LANG— pitch content is now generated byPOST /api/v1/ai/workflow/test/connection {url, language}and written to/save/pitchwith the flow'sworkflowId. No more local pitch.json input.--language autosetsuseConfigLanguage=true.- Automatic 3-step skeleton seed (1h / 3d / 7d delays) when
POST /get/promptreturns an empty list for a fresh flow, viawizard.seed_default_steps→/save/promptwith empty rows → re-query. Backs the frontend's hard-coded 3-step default pipeline. --regenerate-emailsis on by default — everyaiflow createrun now callsPOST /get/email/promptper step (SSE-parsed) to fill theemailContentprompt template, so the scheduler has something real to feed the LLM at send time. Pass--no-regenerate-emailsto skip (scaffolding only; launch will be blocked until populated viaaiflow prompt-update).- Launch-time completeness gate (
AIFLOW_LAUNCH_PROMPTS_INCOMPLETE) blocks--launchwhen any step'semailContentis still empty. --enable-agent-reply/--no-enable-agent-replyand--agent-strategyoverrides for the/save/settingbody (inherits from/get/settingwhen not passed).- Auto-patch
agentPromptList[Book Meeting].meetingRouteIdon launch by picking[0].idfromPOST /meeting-svc/api/v1/meeting/personal/list, mirroring search-websiteai-sdr/v2/components/ai-reply.vue:192-194. - 3-attempt retry + 2s backoff in
wizard.regenerate_step_contentfor the flaky ai-sdr/Cloudflare chain on test env.
aiflow pitch-update FLOW_ID --url URL [--language]— rerun/test/connectionand overwrite the saved pitch.aiflow prompt-show FLOW_ID [--step N] [--full]— dump the savedemailContentper step (uses/get/email/prompt's short-circuit path withworkflowStepId).aiflow prompt-update FLOW_ID --file prompts.json— REPLACE the step list via/save/prompt(warning: deletes any existing step not in the uploaded list).aiflow settings-update FLOW_ID [--time-template-id N] [--mailboxes MODE] [--auto-approve] [--enable-agent-reply] [--agent-strategy S]— fetch/get/setting, merge the passed flags onto the returned body (keepsagentPromptList/emailTrackpassthrough), POST/save/setting. Auto-patches Book MeetingmeetingRouteIdwhenisShowAiReply=Trueand no router is bound yet.
aiflow draft—GET /api/v1/ai/workflow/draft(shows the user's most recent DRAFT; read-only, no resume).aiflow setting-show FLOW_ID—GET /api/v1/ai/workflow/get/setting/{id}.meetings list [--name N] [--type T]— meeting-svc personal routers, new top-level command group.
meeting_prefixconfig key with default/meeting-svc(reuses the existing auth-gateway-svc route).FLASHREV_AIFLOW_MEETING_PREFIXenv override.auth statusJSON payload now includesmeeting_prefix.
- First-run welcome banner. Running the bare
flashclaw-cli-plugin-flashrev-aiflowcommand (no subcommand) afterpipx installprints a green "installed successfully" message plus a 3-step onboarding checklist that points at https://info.flashlabs.ai/settings/privateApps for API key creation, shows the exactauth logincommand, and ends withauth whoamias a verification step. Banner auto-suppresses once a key is bound. - Missing API key prompt now points to https://info.flashlabs.ai/settings/privateApps for key creation.
- Insufficient / exhausted token balance prompts point to https://info.flashlabs.ai/settings/credit for top-up.
- All user-facing text standardized to English (CSV column heuristics
like
邮箱 / 国家stay bilingual since they match user CSV data, not CLI prompts).
aiflow createaccepts--url(with--websiteas alias) instead of--pitch-file+--website. Pitch is now always LLM-generated; the local JSON-pitch path is gone.aiflow pitch show→aiflow pitch-show(flat command, no group).aiflow createdefault behaviour: LLM prompt templates are generated automatically (--regenerate-emails=True); previous opt-in flag semantics are inverted./save/pitchclient path: was/api/v1/ai/workflow/agent/save/pitch, now/api/v1/ai/workflow/save/pitch(drops the redundant/agent/).test_website_connection(url)now requires alanguageargument; the DTO body is{url, language}rather than{url}.- auth-gateway-svc
/meeting-svcroute target was stripped of its trailing/meeting— the CLI now carries the full/meeting/api/v1/meeting/personal/listpath to mirror/flashrev. (Paired commit inauth-gateway-svc.) - Client
save_prompt(workflow_id, prompts)signature, replacing the oldsave_workflow_prompt(payload)(removed).
aiflow pitch initcommand (and_pitch_scaffold) — pitch is now LLM-driven from--url; the local JSON scaffold served a workflow that no longer exists.aiflow templatecommand.- Client methods that have no CLI binding or hit obsolete paths:
get_time_template,save_workflow_prompt,save_bind_email,get_email_config,save_email_config,get_auto_approve_status,save_auto_approve_status,batch_delete_aiflow,get_mailbox_pool_detail.
- Cap CLI-supplied
emailContent(and the other three TEXT-mapped prompt fields) at 2/3 of the MySQL TEXT column limit (43,690 bytes —_EMAIL_CONTENT_BYTE_LIMITinwizard.py). LLM templates regenerated by--regenerate-emailswere occasionally exceeding the column ceiling, which the backend reported as HTTP 200 + body{code: 400, msg: "Data truncation: Data too long for column 'email_content'"}; the CLI's_handleonly checks the HTTP status, so the failure was slipping through silently as a "successful" save with empty content on read-back. The truncation is byte-aware (handles utf-8 multi-byte boundaries viadecode("utf-8", errors="ignore")) and emits a stderr warning per truncated step so operators can see the cap fire. Applied at the source inregenerate_step_contentand defensively inbuild_save_prompt_bodysoaiflow prompt-update --fileis protected too. /get/email/promptshort-circuit:regenerate_step_contentno longer passesworkflowStepIdon the request. The backend short-circuits to the saved (empty) row whenever it sees a matching id, which was suppressing the LLM path for freshly-seeded rows.- SSE parser (
wizard._parse_sse_content) strips the backend's</content>stream terminator so downstream consumers see a clean prompt template. /get/settingresponse shape:propertieslives at the top level ofdata, not underdata.setting.propertiesas an older doc suggested;build_save_setting_body+settings-updatenow read the correct path and preserveemailTrack/timeTemplateConfigon partial updates.timeTemplateConfigprecedence: when the CLI supplies a concrete template (e.g.--time-template-id), it now overrides the already-persisted one instead of silently reusing the existing.get_meeting_prefiximport kept pinned with an explicit# noqa: F401comment; the shared formatter was stripping it repeatedly.- 502/504 transient upstream failures during the regenerate pass now retry 3 times with 2s backoff before giving up on a step.
- 84 passing (was 41). New coverage: V2 endpoints (
test_connectionwith language,save_pitchpath fix,get_email_promptbody shape,get_setting/draft/list_personal_meetingspaths, meeting prefix routing),_parse_sse_content+seed_default_steps+patch_meeting_route_id+pick_first_meeting_router_id,build_save_setting_body(DRAFT / flag overrides / agent block visibility), all four edit commands (pitch-update / prompt-show / prompt-update / settings-update), launch-completeness gate, the first-run welcome banner (shown-when-unconfigured + suppressed-when-bound), and the email-content byte-limit cap (passes-through under limit, truncates over limit, safe at multi-byte UTF-8 boundaries, applied at build_save_prompt_body).
README.mdrewritten: V2 pipeline table,--no-wizardrequired-field matrix, launch-gate semantics,--no-regenerate-emailsopt-out.skills/SKILL.mdcommand manifest synced (27 commands across 6 groups).
Milestones M1–M5 from the PRD are implemented. Endpoints are all real
paths observed in the search-website frontend; no endpoint was invented
from the PRD spec.
pyproject.tomldeclaring the package asflashclaw-cli-plugin-flashrev-aiflow- Nested
flashclaw_cli_plugin/flashrev_aiflow/PEP 420 namespace package (shared withflashclaw-cli-plugin-call-svc); seeflashclaw_cli_plugin/NAMESPACE.md auth login --token KEY— saves API key to~/.claude/skills/flashrev-aiflow-assistant/.envauth logout— clears the saved keyauth whoami— callsGET /api/v2/oauth/me, validates the key, returns identity + equityauth status— shows local-only config without any network callconfig show / set / unset— runtime config forbase_url,timeout, and the three gateway prefixes (discover_prefixdefault/flashrev,engage_prefixdefault empty,mailsvc_prefixdefault empty)health— unauthenticated gateway reachability check- Click-based CLI with global
--jsonfor agent-friendly structured output utils/output.pyemits JSON or human output + structured errors with anerrorcode + exit code 1
aiflow list [--status] [--page] [--page-size]→GET /api/v1/ai/workflow/agent/listaiflow show FLOW_ID→GET /api/v1/ai/workflow/agent/get/flow/{flowId}aiflow pitch FLOW_ID→GET /api/v1/ai/workflow/agent/get/pitch(nowaiflow pitch show)aiflow template→GET /api/v1/ai/workflow/agent/get/time/templateaiflow test-connection URL→POST /api/v1/ai/workflow/agent/test/connectionaiflow delete FLOW_ID [-y]/aiflow rename FLOW_ID NEW_NAMEmailboxes list [--status] [--warmup]→GET /mailsvc/mail-address/simple/v2/listmailboxes bind-list/mailboxes has-active
aiflow createwizard spanning Strategy → AIFlow → Settings → Launch, using:POST /api/v1/ai/workflow/contacts/upload(new backend endpoint — seeREADME.md)POST /api/v1/ai/workflow/create/list→ returns{id: flowId}POST /api/v1/ai/workflow/save/pitch→ persists the 6 sectionsGET /api/v1/ai/workflow/agent/get/time/template→ read-only previewPOST /api/v1/ai/workflow/agent/get/email/config→ load current settingsPOST /api/v1/ai/workflow/agent/save/email/config→ save with editsGET /api/v1/ai/workflow/agent/sequence/status/{flowId}/ACTIVE→ launch
wizard.pyhelpers: CSV/Google-Sheet loading (/export?format=csvtrick), BOM-safe CSV preview + email-dedup, EN/CN heuristic country-column detection (country / region / location / 国家 / 地区), interactiveclick.edit-based pitch editor with retry, mailbox filter for active-only entries- Pitch via either
--pitch-file path.jsonor$EDITOR; pitch file may carry theurl/languagefields so--websiteis optional - Correct status tokens (
ACTIVE/PAUSED) derived from frontend code
--no-wizard— every missing required field raises aUSAGE_*error with exit code 2 (CI-friendly, no prompts)--dry-run— validates inputs and probes read-only endpoints (token balance, mailbox list, time template) but skips every write endpoint; emits a summary with adryRun: truemarker--country-column COL | none— explicit flag for auto-language mode (required in--no-wizard);noneexplicitly opts out- Required-field matrix with dedicated error codes:
USAGE_CONTACT_SOURCE,USAGE_CONTACT_SOURCE_CONFLICT,USAGE_PITCH_FILE_REQUIRED,USAGE_WEBSITE_MISSING,USAGE_COUNTRY_COLUMN_MISSING,USAGE_COUNTRY_COLUMN_NOT_FOUND,USAGE_MAILBOX_NO_MATCH - Launch defaults in
--no-wizardmode: save as draft unless--launchis explicitly passed
token balance— derives{tokenTotal, tokenCost, tokenRemaining, sufficient}fromGET /api/v2/oauth/me→data.limit.*token check [--required N]— exits 2 (TOKEN_INSUFFICIENTorTOKEN_EXHAUSTED) when balance is below the requested amountaiflow start/aiflow resumerun the same balance check pre-flight;--forceskips the CLI-side check (backendauth-gateway-svcenforcement still applies where configured)- Fail-open on
/meerrors — emits a stderr warning rather than blocking every launch when the /me endpoint is flaky
- New endpoint
POST /api/v1/ai/workflow/contacts/upload(multipart file upload) required foraiflow createend-to-end. Spec inREADME.md; a reference implementation fordubbo-api-svcexists on thefeature/openclaw-mail-cli-260421branch, already merged intotest.
- 41 unit tests covering session + client URL building + token balance math + wizard helpers (CSV / Sheet / pitch / mailbox filter) + non-wizard flag matrix + dry-run contract + pitch init