@elizaos/plugin-form 2.0.0-alpha.1 → 2.0.0-alpha.2

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/README.md DELETED
@@ -1,846 +0,0 @@
1
- # @elizaos/plugin-form
2
-
3
- **Guardrails for agent-guided user journeys.**
4
-
5
- ---
6
-
7
- **Architecture & Design:** Odilitime
8
- **Copyright © 2025 Odilitime.** Licensed under MIT.
9
- See [Credits & Architecture](#credits--architecture) section below.
10
-
11
- ---
12
-
13
- ## The Insight
14
-
15
- Agents are powerful but unpredictable. Without structure, they wander. They forget what they're collecting. They miss required information. They can't reliably guide users to outcomes.
16
-
17
- **Forms are guardrails.** They give agents:
18
-
19
- - **A path to follow** - structured steps toward an outcome
20
- - **Conventions to honor** - required fields, validation rules, confirmation flows
21
- - **Memory of progress** - what's been collected, what's missing, what needs confirmation
22
- - **Recovery from interruption** - stash, restore, pick up where you left off
23
-
24
- This plugin turns agents into **reliable process guides** that can shepherd users through registrations, orders, applications, onboarding flows - any structured journey to an outcome.
25
-
26
- ## How It Works
27
-
28
- The user talks naturally. The agent stays on track:
29
-
30
- ```
31
- User: "I'm John Smith, 28 years old, and you can reach me at john@example.com"
32
- Agent: "Got it! I have your name as John Smith, age 28, and email john@example.com.
33
- I still need your username. What would you like to use?"
34
- ```
35
-
36
- The agent knows:
37
- - ✅ What's been collected (name, age, email)
38
- - ❌ What's still needed (username)
39
- - 🎯 What to ask next
40
- - 🛤️ How to get to the outcome (account creation)
41
-
42
- ## Use Cases: User Journeys
43
-
44
- Forms enable any structured journey:
45
-
46
- | Journey | Outcome | Guardrails Needed |
47
- |---------|---------|-------------------|
48
- | **User Registration** | Account created | Email, username, age verification |
49
- | **Product Order** | Order placed | Product selection, billing, payment |
50
- | **Support Ticket** | Issue logged | Category, description, contact info |
51
- | **KYC/Onboarding** | User verified | Identity docs, address, compliance |
52
- | **Booking/Reservation** | Appointment set | Date, time, preferences, contact |
53
- | **Application** | Application submitted | Personal info, qualifications, documents |
54
- | **Feedback Collection** | Insights captured | Rating, comments, follow-up consent |
55
-
56
- Each journey has required stops. Forms ensure the agent visits them all.
57
-
58
- ## Features
59
-
60
- - 🛤️ **Journey Guardrails** - Agents follow structured paths to outcomes
61
- - 🗣️ **Natural Language** - Users talk naturally, agents extract structure
62
- - 🔄 **Two-Tier Intent** - Fast English keywords + LLM fallback for any language
63
- - ✨ **UX Magic** - Undo, skip, explain, example, progress, autofill
64
- - ⏰ **Smart TTL** - Retention scales with user effort invested
65
- - 🔌 **Extensible Types** - Register custom validators for your domain
66
- - 📦 **Hooks System** - React to journey lifecycle events
67
- - 💾 **Stash/Restore** - Pause journeys, resume later
68
-
69
- ## Installation
70
-
71
- ```bash
72
- bun add @elizaos/plugin-form
73
- ```
74
-
75
- ## Quick Start
76
-
77
- ### 1. Add plugin to your agent
78
-
79
- ```typescript
80
- import { formPlugin } from '@elizaos/plugin-form';
81
-
82
- const character = {
83
- // ... your character config
84
- plugins: [formPlugin],
85
- };
86
- ```
87
-
88
- ### 2. Define a form
89
-
90
- ```typescript
91
- import { Form, C, FormService } from '@elizaos/plugin-form';
92
-
93
- const registrationForm = Form.create('registration')
94
- .name('User Registration')
95
- .description('Create your account')
96
- .control(
97
- C.email('email')
98
- .required()
99
- .ask('What email should we use for your account?')
100
- .example('user@example.com')
101
- )
102
- .control(
103
- C.text('name')
104
- .required()
105
- .ask("What's your name?")
106
- )
107
- .control(
108
- C.number('age')
109
- .min(13)
110
- .ask('How old are you?')
111
- )
112
- .onSubmit('handle_registration')
113
- .build();
114
- ```
115
-
116
- ### 3. Register the form in your plugin
117
-
118
- ```typescript
119
- import type { Plugin, IAgentRuntime } from '@elizaos/core';
120
- import { FormService } from '@elizaos/plugin-form';
121
-
122
- export const myPlugin: Plugin = {
123
- name: 'my-plugin',
124
- dependencies: ['form'], // Depend on form plugin
125
-
126
- async init(runtime: IAgentRuntime) {
127
- const formService = runtime.getService('FORM') as FormService;
128
- formService.registerForm(registrationForm);
129
- },
130
- };
131
- ```
132
-
133
- ### 4. Start a form session
134
-
135
- ```typescript
136
- // In an action or service:
137
- const formService = runtime.getService('FORM') as FormService;
138
- await formService.startSession('registration', entityId, roomId);
139
- ```
140
-
141
- ### 5. Handle submissions
142
-
143
- ```typescript
144
- runtime.registerTaskWorker({
145
- name: 'handle_registration',
146
- validate: async () => true,
147
- execute: async (runtime, options) => {
148
- const { submission } = options;
149
- const { email, name, age } = submission.values;
150
-
151
- // Create user account, send welcome email, etc.
152
- console.log(`New user: ${name} (${email}), age ${age}`);
153
- },
154
- });
155
- ```
156
-
157
- ## User Experience
158
-
159
- Once a form is active, users can interact naturally:
160
-
161
- | User Says | What Happens |
162
- |-----------|--------------|
163
- | "I'm John, 25 years old" | Extracts name=John, age=25 |
164
- | "john@example.com" | Extracts email |
165
- | "done" / "submit" | Submits the form |
166
- | "save for later" | Stashes form, can switch topics |
167
- | "resume my form" | Restores stashed form |
168
- | "cancel" / "nevermind" | Abandons form |
169
- | "undo" / "go back" | Reverts last change |
170
- | "skip" | Skips optional field |
171
- | "why?" | Explains why field is needed |
172
- | "example?" | Shows example value |
173
- | "how far?" | Shows completion progress |
174
- | "same as last time" | Applies autofill |
175
-
176
- ## API Reference
177
-
178
- ### FormBuilder
179
-
180
- Create forms with the fluent builder API:
181
-
182
- ```typescript
183
- import { Form, C } from '@elizaos/plugin-form';
184
-
185
- const form = Form.create('contact')
186
- // Metadata
187
- .name('Contact Form')
188
- .description('Get in touch with us')
189
- .version(1)
190
-
191
- // Controls
192
- .control(C.email('email').required())
193
- .control(C.text('message').required().maxLength(1000))
194
- .control(C.select('department', [
195
- { value: 'sales', label: 'Sales' },
196
- { value: 'support', label: 'Support' },
197
- ]))
198
-
199
- // Permissions
200
- .roles('user', 'admin')
201
- .allowMultiple()
202
-
203
- // UX options
204
- .noUndo() // Disable undo
205
- .noSkip() // Disable skip
206
- .maxUndoSteps(3) // Limit undo history
207
-
208
- // TTL (retention)
209
- .ttl({ minDays: 7, maxDays: 30, effortMultiplier: 0.5 })
210
-
211
- // Nudge (reminders)
212
- .nudgeAfter(24) // Hours before nudge
213
- .nudgeMessage('You have an unfinished contact form...')
214
-
215
- // Hooks
216
- .onStart('on_contact_start')
217
- .onFieldChange('on_contact_field_change')
218
- .onReady('on_contact_ready')
219
- .onSubmit('handle_contact_submission')
220
- .onCancel('on_contact_cancel')
221
-
222
- // Debug
223
- .debug() // Enable extraction logging
224
-
225
- .build();
226
- ```
227
-
228
- ### ControlBuilder
229
-
230
- Create field controls:
231
-
232
- ```typescript
233
- import { C } from '@elizaos/plugin-form';
234
-
235
- // Basic types
236
- C.text('name')
237
- C.email('email')
238
- C.number('age')
239
- C.boolean('subscribe')
240
- C.date('birthdate')
241
- C.file('resume')
242
- C.select('country', [{ value: 'us', label: 'United States' }])
243
-
244
- // Validation
245
- C.text('username')
246
- .required()
247
- .minLength(3)
248
- .maxLength(20)
249
- .pattern('^[a-z0-9_]+$')
250
-
251
- C.number('quantity')
252
- .min(1)
253
- .max(100)
254
-
255
- // Agent hints
256
- C.text('company')
257
- .label('Company Name')
258
- .description('The company you work for')
259
- .ask('What company do you work for?')
260
- .hint('employer', 'organization', 'workplace')
261
- .example('Acme Corp')
262
- .confirmThreshold(0.9) // Require high confidence
263
-
264
- // Behavior
265
- C.text('password')
266
- .required()
267
- .sensitive() // Don't echo back
268
-
269
- C.text('internalId')
270
- .hidden() // Extract silently, never ask
271
-
272
- C.text('role')
273
- .readonly() // Can't change after set
274
-
275
- // File uploads
276
- C.file('document')
277
- .accept(['application/pdf', 'image/*'])
278
- .maxSize(5 * 1024 * 1024) // 5MB
279
- .maxFiles(3)
280
-
281
- // Access control
282
- C.number('discount')
283
- .roles('admin', 'sales')
284
-
285
- // Conditional fields
286
- C.text('state')
287
- .dependsOn('country', 'equals', 'us')
288
-
289
- // Database mapping
290
- C.text('firstName')
291
- .dbbind('first_name')
292
-
293
- // UI hints (for future GUIs)
294
- C.text('bio')
295
- .section('Profile')
296
- .order(1)
297
- .placeholder('Tell us about yourself...')
298
- .widget('textarea')
299
- ```
300
-
301
- ### FormService
302
-
303
- The main service for managing forms:
304
-
305
- ```typescript
306
- const formService = runtime.getService('FORM') as FormService;
307
-
308
- // Form definitions
309
- formService.registerForm(form);
310
- formService.getForm('form-id');
311
- formService.listForms();
312
-
313
- // Sessions
314
- await formService.startSession('form-id', entityId, roomId);
315
- await formService.getActiveSession(entityId, roomId);
316
- await formService.getStashedSessions(entityId);
317
-
318
- // Field updates
319
- await formService.updateField(sessionId, entityId, 'email', 'john@example.com', 0.95, 'extraction');
320
- await formService.undoLastChange(sessionId, entityId);
321
- await formService.skipField(sessionId, entityId, 'nickname');
322
-
323
- // Lifecycle
324
- await formService.submit(sessionId, entityId);
325
- await formService.stash(sessionId, entityId);
326
- await formService.restore(sessionId, entityId);
327
- await formService.cancel(sessionId, entityId);
328
-
329
- // Autofill
330
- await formService.applyAutofill(session);
331
-
332
- // Context
333
- formService.getSessionContext(session);
334
-
335
- // Custom types
336
- formService.registerType('phone', {
337
- validate: (value) => ({
338
- valid: /^\+?[\d\s-()]+$/.test(String(value)),
339
- error: 'Invalid phone number',
340
- }),
341
- extractionPrompt: 'a phone number (digits, spaces, dashes allowed)',
342
- });
343
- ```
344
-
345
- ### Hooks
346
-
347
- React to form lifecycle events by registering task workers:
348
-
349
- ```typescript
350
- // Called when session starts
351
- runtime.registerTaskWorker({
352
- name: 'on_registration_start',
353
- execute: async (runtime, { session, form }) => {
354
- // Log, initialize context, etc.
355
- },
356
- });
357
-
358
- // Called when any field changes
359
- runtime.registerTaskWorker({
360
- name: 'on_registration_field_change',
361
- execute: async (runtime, { session, field, value, oldValue }) => {
362
- // Validate cross-field dependencies, update derived values
363
- },
364
- });
365
-
366
- // Called when all required fields are filled
367
- runtime.registerTaskWorker({
368
- name: 'on_registration_ready',
369
- execute: async (runtime, { session }) => {
370
- // Maybe auto-submit, or prepare preview
371
- },
372
- });
373
-
374
- // Called on successful submission
375
- runtime.registerTaskWorker({
376
- name: 'handle_registration',
377
- execute: async (runtime, { session, submission }) => {
378
- const { email, name, age } = submission.values;
379
- // Create account, send email, etc.
380
- },
381
- });
382
-
383
- // Called when user cancels
384
- runtime.registerTaskWorker({
385
- name: 'on_registration_cancel',
386
- execute: async (runtime, { session }) => {
387
- // Cleanup, log abandonment, etc.
388
- },
389
- });
390
-
391
- // Called when session expires
392
- runtime.registerTaskWorker({
393
- name: 'on_registration_expire',
394
- execute: async (runtime, { session }) => {
395
- // Final cleanup
396
- },
397
- });
398
- ```
399
-
400
- ## Custom Field Types
401
-
402
- Register custom validators for domain-specific types:
403
-
404
- ```typescript
405
- import { registerTypeHandler } from '@elizaos/plugin-form';
406
-
407
- // Solana wallet address
408
- registerTypeHandler('solana_address', {
409
- validate: (value) => {
410
- const valid = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(String(value));
411
- return { valid, error: valid ? undefined : 'Invalid Solana address' };
412
- },
413
- parse: (value) => value.trim(),
414
- format: (value) => `${String(value).slice(0, 4)}...${String(value).slice(-4)}`,
415
- extractionPrompt: 'a Solana wallet address (Base58 encoded, 32-44 characters)',
416
- });
417
-
418
- // Use in form
419
- const form = Form.create('wallet')
420
- .control(
421
- C.field('walletAddress')
422
- .type('solana_address')
423
- .required()
424
- .label('Wallet Address')
425
- )
426
- .build();
427
- ```
428
-
429
- ## Widget Registry (ControlType System)
430
-
431
- The Widget Registry is a powerful system for creating complex, service-backed control types. It enables plugins to register custom controls that go beyond simple validation.
432
-
433
- ### Three Types of Controls
434
-
435
- | Type | Description | Example |
436
- |------|-------------|---------|
437
- | **Simple** | Validate/parse/format only | text, email, phone |
438
- | **Composite** | Has subcontrols that roll up | address (street, city, zip) |
439
- | **External** | Requires async external confirmation | payment, signature |
440
-
441
- ### Registering a Simple Type
442
-
443
- ```typescript
444
- const formService = runtime.getService('FORM') as FormService;
445
-
446
- formService.registerControlType({
447
- id: 'phone',
448
- validate: (value) => ({
449
- valid: /^\+?[\d\s-()]+$/.test(String(value)),
450
- error: 'Invalid phone number',
451
- }),
452
- parse: (value) => value.replace(/\s/g, ''),
453
- format: (value) => String(value),
454
- extractionPrompt: 'a phone number with optional country code',
455
- });
456
- ```
457
-
458
- ### Registering a Composite Type
459
-
460
- Composite types have subcontrols that must all be filled:
461
-
462
- ```typescript
463
- formService.registerControlType({
464
- id: 'address',
465
-
466
- // Returns subcontrols for this type
467
- getSubControls: (control, runtime) => [
468
- { key: 'street', type: 'text', label: 'Street', required: true },
469
- { key: 'city', type: 'text', label: 'City', required: true },
470
- { key: 'state', type: 'text', label: 'State', required: true },
471
- { key: 'zip', type: 'text', label: 'ZIP Code', required: true },
472
- ],
473
-
474
- // Validate the combined address
475
- validate: (value) => ({ valid: true }),
476
- });
477
-
478
- // Use in form
479
- const form = Form.create('shipping')
480
- .control(C.field('address').type('address').required())
481
- .build();
482
- ```
483
-
484
- When users provide address parts, they're extracted as subfields:
485
- ```
486
- User: "123 Main St, Springfield, IL 62701"
487
- → Extracts: address.street, address.city, address.state, address.zip
488
- ```
489
-
490
- ### Registering an External Type
491
-
492
- External types require async confirmation from outside the conversation (blockchain transactions, signatures, etc.):
493
-
494
- ```typescript
495
- formService.registerControlType({
496
- id: 'payment',
497
-
498
- getSubControls: (control, runtime) => [
499
- { key: 'amount', type: 'number', label: 'Amount', required: true },
500
- { key: 'currency', type: 'select', label: 'Currency', required: true,
501
- options: [{ value: 'SOL', label: 'Solana' }, { value: 'ETH', label: 'Ethereum' }]
502
- },
503
- ],
504
-
505
- // Called when all subcontrols are filled
506
- activate: async (context) => {
507
- const { session, subValues, runtime } = context;
508
- const paymentService = runtime.getService('PAYMENT');
509
-
510
- // Create pending payment, return instructions
511
- return paymentService.createPendingPayment({
512
- sessionId: session.id,
513
- amount: subValues.amount,
514
- currency: subValues.currency,
515
- });
516
- // Returns: { instructions: "Send 0.5 SOL to xyz...", reference: "abc123" }
517
- },
518
-
519
- // Called when user cancels
520
- deactivate: async (context) => {
521
- const paymentService = context.runtime.getService('PAYMENT');
522
- await paymentService.cancelPending(context.session.fields.payment?.externalState?.reference);
523
- },
524
-
525
- validate: (value) => ({
526
- valid: !!value?.confirmed,
527
- error: 'Payment not confirmed',
528
- }),
529
- });
530
- ```
531
-
532
- ### External Confirmation Flow
533
-
534
- External types follow an event-driven confirmation flow:
535
-
536
- ```
537
- 1. User fills subcontrols: "$50 in SOL"
538
- 2. Evaluator detects all subcontrols filled
539
- 3. Evaluator calls formService.activateExternalField()
540
- 4. Widget's activate() creates pending request, returns instructions
541
- 5. Agent shows instructions: "Send 0.5 SOL to xyz..."
542
- 6. (External) User sends blockchain transaction
543
- 7. PaymentService detects tx, calls formService.confirmExternalField()
544
- 8. Field status changes to 'filled'
545
- 9. Agent proceeds with form
546
- ```
547
-
548
- ### Confirming External Fields
549
-
550
- When your service detects the external action completed:
551
-
552
- ```typescript
553
- // In your payment service
554
- async handlePaymentReceived(reference: string, txData: any) {
555
- const pending = this.getPendingByReference(reference);
556
-
557
- const formService = this.runtime.getService('FORM') as FormService;
558
- await formService.confirmExternalField(
559
- pending.sessionId,
560
- pending.entityId,
561
- pending.field,
562
- { confirmed: true, txId: txData.signature },
563
- { blockchain: 'solana', confirmedAt: Date.now() }
564
- );
565
- }
566
- ```
567
-
568
- ### Widget Events
569
-
570
- The evaluator emits events as it processes messages:
571
-
572
- | Event | When | Payload |
573
- |-------|------|---------|
574
- | `FORM_FIELD_EXTRACTED` | Simple field value extracted | sessionId, field, value, confidence |
575
- | `FORM_SUBFIELD_UPDATED` | Composite subfield updated | sessionId, parentField, subField, value |
576
- | `FORM_SUBCONTROLS_FILLED` | All subcontrols filled | sessionId, field, subValues |
577
- | `FORM_EXTERNAL_ACTIVATED` | External type activated | sessionId, field, activation |
578
- | `FORM_FIELD_CONFIRMED` | External field confirmed | sessionId, field, value, externalData |
579
- | `FORM_FIELD_CANCELLED` | External field cancelled | sessionId, field, reason |
580
-
581
- Widgets react to events, they don't parse messages.
582
-
583
- ### Built-in Types
584
-
585
- These types are pre-registered and protected from override:
586
-
587
- | Type | Validation | Extraction Prompt |
588
- |------|------------|-------------------|
589
- | `text` | Pattern, length | "a text string" |
590
- | `number` | Min/max value | "a number" |
591
- | `email` | Email format | "an email address" |
592
- | `boolean` | Yes/no values | "yes/no or true/false" |
593
- | `select` | Must match option | "one of the options" |
594
- | `date` | Valid date | "a date in YYYY-MM-DD" |
595
- | `file` | Size, type limits | "a file attachment" |
596
-
597
- Override built-in types with caution:
598
-
599
- ```typescript
600
- formService.registerControlType(myCustomText, { allowOverride: true });
601
- ```
602
-
603
- ## Architecture
604
-
605
- ```
606
- ┌─────────────────────────────────────────────────────────────┐
607
- │ User Message │
608
- └────────────────────────────┬────────────────────────────────┘
609
-
610
-
611
- ┌─────────────────────────────────────────────────────────────┐
612
- │ FORM_CONTEXT Provider │
613
- │ • Runs BEFORE agent responds │
614
- │ • Injects: progress, filled fields, next field, etc. │
615
- │ • Shows pending external actions (payments, signatures) │
616
- │ • Agent knows what to ask │
617
- └────────────────────────────┬────────────────────────────────┘
618
-
619
-
620
- ┌─────────────────────────────────────────────────────────────┐
621
- │ Agent (REPLY) │
622
- │ • Uses form context to craft response │
623
- │ • Asks for next field, confirms uncertain values │
624
- │ • Shows external action instructions when pending │
625
- └────────────────────────────┬────────────────────────────────┘
626
-
627
-
628
- ┌─────────────────────────────────────────────────────────────┐
629
- │ form_evaluator │
630
- │ • Runs AFTER each message │
631
- │ • Tier 1: Fast English keyword matching │
632
- │ • Tier 2: LLM extraction for complex/non-English │
633
- │ • Handles subfields for composite types │
634
- │ • Emits events (FORM_FIELD_EXTRACTED, etc.) │
635
- │ • Triggers activation for external types │
636
- │ • Updates session state │
637
- └────────────────────────────┬────────────────────────────────┘
638
-
639
-
640
- ┌─────────────────────────────────────────────────────────────┐
641
- │ FormService │
642
- │ ┌──────────────────────────────────────────────────────┐ │
643
- │ │ ControlType Registry │ │
644
- │ │ • Built-in: text, number, email, boolean, etc. │ │
645
- │ │ • Custom: phone, address, payment, signature │ │
646
- │ │ • Simple → Composite → External types │ │
647
- │ └──────────────────────────────────────────────────────┘ │
648
- │ • Manages form definitions │
649
- │ • Manages sessions (create, update, stash, restore) │
650
- │ • Handles subfields for composite types │
651
- │ • Activates/confirms/cancels external fields │
652
- │ • Executes lifecycle hooks │
653
- │ • Handles submissions │
654
- └─────────────────────────────────────────────────────────────┘
655
- ```
656
-
657
- ### Widget Registry Flow (External Types)
658
-
659
- ```
660
- ┌──────────────┐ ┌────────────────┐ ┌──────────────────┐
661
- │ User │ │ Evaluator │ │ FormService │
662
- │ "$50 SOL" │───▶│ Extract values │───▶│ updateSubField() │
663
- └──────────────┘ │ Emit events │ │ areSubFieldsFilled? │
664
- └───────┬────────┘ └────────┬─────────┘
665
- │ │ Yes
666
- │ ┌───────▼────────┐
667
- │ │ activateExternal│
668
- │ │ Field() │
669
- │ └───────┬─────────┘
670
- │ │
671
- ┌───────▼──────────────────────▼─────────┐
672
- │ ControlType.activate() │
673
- │ • Creates pending request │
674
- │ • Returns instructions + reference │
675
- └───────────────────────────┬────────────┘
676
-
677
- ┌───────────────────────────▼────────────┐
678
- │ Agent Shows Instructions │
679
- │ "Send 0.5 SOL to xyz... ref: abc123" │
680
- └────────────────────────────────────────┘
681
- ...
682
- ┌──────────────┐ ┌────────────────┐ ┌──────────────────┐
683
- │ Blockchain │ │ PaymentService │ │ FormService │
684
- │ TX detected │───▶│ Match reference│───▶│confirmExternal() │
685
- └──────────────┘ └────────────────┘ └──────────────────┘
686
-
687
- ┌───────────────────────────▼────────────┐
688
- │ Field marked 'filled' │
689
- │ Form continues... │
690
- └────────────────────────────────────────┘
691
- ```
692
-
693
- ## Smart TTL
694
-
695
- Form sessions are retained based on user effort:
696
-
697
- | Time Spent | Retention |
698
- |------------|-----------|
699
- | 0-5 min | 14 days (minimum) |
700
- | 30 min | ~15 days |
701
- | 2 hours | ~60 days |
702
- | 4+ hours | 90 days (maximum) |
703
-
704
- Configure per-form:
705
-
706
- ```typescript
707
- Form.create('important-form')
708
- .ttl({
709
- minDays: 30, // Always keep at least 30 days
710
- maxDays: 180, // Never more than 180 days
711
- effortMultiplier: 1, // 1 day per minute of effort
712
- })
713
- ```
714
-
715
- ## Debugging
716
-
717
- Enable debug mode to see extraction reasoning:
718
-
719
- ```typescript
720
- Form.create('debug-form')
721
- .debug() // Logs LLM extraction reasoning
722
- .build();
723
- ```
724
-
725
- ---
726
-
727
- ## Credits & Architecture
728
-
729
- ### Design & Architecture
730
-
731
- **Plugin-Form** was architected and designed by **Odilitime**.
732
-
733
- #### Core Insight
734
-
735
- **Forms as Agent Guardrails (2025)**
736
-
737
- The foundational insight: forms aren't about data collection—they're about **keeping agents on track**. Without structure, agents wander, forget context, and fail to guide users to outcomes. This plugin provides the rails that make agents reliable process guides.
738
-
739
- #### Core Innovations
740
-
741
- **Agent-Native Form Architecture**
742
- - Designed the evaluator-driven extraction pattern (vs action-per-field)
743
- - Created the provider-based context injection for agent awareness
744
- - Invented two-tier intent detection (fast English + LLM fallback)
745
- - Result: Agents naturally guide users through journeys without explicit commands
746
-
747
- **Conversational UX System**
748
- - Designed natural language "UX magic" commands (undo, skip, explain, example, progress)
749
- - Created confidence-based confirmation flow for uncertain extractions
750
- - Invented the stash/restore pattern for journey interruption
751
- - Result: Users interact naturally, agents stay on track
752
-
753
- **Effort-Based TTL System**
754
- - Designed smart retention that scales with user investment
755
- - Created the effort tracking model (time spent, interaction count)
756
- - Invented nudge system for stale journeys
757
- - Result: Respects user work while managing storage
758
-
759
- **Fluent Builder API**
760
- - Designed type-safe form definition syntax
761
- - Created the ControlBuilder/FormBuilder pattern
762
- - Invented shorthand factories (C.email, C.number, etc.)
763
- - Result: Forms are easy to define, hard to misconfigure
764
-
765
- **Extensible Type System**
766
- - Designed pluggable type handler registry
767
- - Created validation/parsing/formatting/extraction pipeline per type
768
- - Enabled domain-specific types (blockchain addresses, phone numbers)
769
- - Result: Any domain can extend the form system
770
-
771
- **Widget Registry (ControlType System)**
772
- - Designed unified ControlType interface for simple, composite, and external types
773
- - Created subfield extraction pattern for composite types
774
- - Invented external type activation/confirmation flow for async processes
775
- - Event-driven architecture: evaluator emits, widgets react
776
- - Override protection for built-in types
777
- - Result: Plugins can register complex, service-backed controls (payments, signatures)
778
-
779
- #### Technical Architecture
780
-
781
- - Evaluator + Provider pattern (not action-per-intent)
782
- - Two-tier intent detection (regex fast path, LLM fallback)
783
- - Component-based storage (room-scoped sessions)
784
- - Confidence-scored extraction with confirmation flow
785
- - Field-level history for undo functionality
786
- - Hook system for consuming plugin integration
787
- - Smart TTL with effort multipliers
788
- - Nudge task worker for stale session recovery
789
- - Widget registry with ControlType interface (simple → composite → external)
790
- - Subfield state tracking for composite types
791
- - External state machine (pending → confirmed/failed/expired)
792
- - Event emission for reactive widget architecture
793
-
794
- #### Philosophy & Principles
795
-
796
- - "Forms are guardrails, not data collectors"
797
- - "Agents follow paths, users reach outcomes"
798
- - "Fast path first, LLM when needed"
799
- - "Respect user effort in retention"
800
- - "Extensibility through types, not code changes"
801
- - "Provider gives context, agent handles conversation"
802
-
803
- ---
804
-
805
- ### Documentation & Knowledge Transfer
806
-
807
- **All documentation authored by Odilitime:**
808
- - Comprehensive README with architecture diagrams
809
- - WHY comments throughout codebase explaining design decisions
810
- - Module-level documentation on every file
811
- - Four complete example plugins demonstrating patterns
812
- - Type definitions with extensive JSDoc
813
-
814
- **Code Quality:**
815
- - Full TypeScript with strict typing
816
- - Fluent builder API with method chaining
817
- - Comprehensive defaults system
818
- - Zero build errors
819
- - Production-ready foundation
820
-
821
- ---
822
-
823
- ### License
824
-
825
- Copyright © 2025 Odilitime
826
-
827
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
828
-
829
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
830
-
831
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
832
-
833
- ---
834
-
835
- ### Recognition
836
-
837
- **If this plugin makes your agent-guided journeys reliable, consider:**
838
- - ⭐ Starring the repository
839
- - 🗣️ Sharing your implementation patterns
840
- - 💬 Contributing improvements back to the community
841
- - 🙏 Acknowledging Odilitime's architectural work
842
-
843
- ---
844
-
845
- **Designed and built by Odilitime. Making agents reliable guides through user journeys.**
846
-