@elizaos/plugin-form 2.0.0-alpha.3 → 2.0.0-alpha.4

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.
@@ -105,7 +105,7 @@ var init_builtins = __esm(() => {
105
105
  return { valid: true };
106
106
  }
107
107
  const num = typeof value === "number" ? value : parseFloat(String(value));
108
- if (isNaN(num)) {
108
+ if (Number.isNaN(num)) {
109
109
  return { valid: false, error: "Must be a valid number" };
110
110
  }
111
111
  if (control.min !== undefined && num < control.min) {
@@ -124,7 +124,7 @@ var init_builtins = __esm(() => {
124
124
  if (value === null || value === undefined)
125
125
  return "";
126
126
  const num = typeof value === "number" ? value : parseFloat(String(value));
127
- if (isNaN(num))
127
+ if (Number.isNaN(num))
128
128
  return String(value);
129
129
  return num.toLocaleString();
130
130
  },
@@ -218,14 +218,14 @@ var init_builtins = __esm(() => {
218
218
  return { valid: false, error: "Must be in YYYY-MM-DD format" };
219
219
  }
220
220
  const date = new Date(str);
221
- if (isNaN(date.getTime())) {
221
+ if (Number.isNaN(date.getTime())) {
222
222
  return { valid: false, error: "Invalid date" };
223
223
  }
224
224
  return { valid: true };
225
225
  },
226
226
  parse: (value) => {
227
227
  const date = new Date(value);
228
- if (!isNaN(date.getTime())) {
228
+ if (!Number.isNaN(date.getTime())) {
229
229
  return date.toISOString().split("T")[0];
230
230
  }
231
231
  return value.trim();
@@ -234,7 +234,7 @@ var init_builtins = __esm(() => {
234
234
  if (!value)
235
235
  return "";
236
236
  const date = new Date(String(value));
237
- if (isNaN(date.getTime()))
237
+ if (Number.isNaN(date.getTime()))
238
238
  return String(value);
239
239
  return date.toLocaleDateString();
240
240
  },
@@ -243,7 +243,7 @@ var init_builtins = __esm(() => {
243
243
  fileType = {
244
244
  id: "file",
245
245
  builtin: true,
246
- validate: (value, control) => {
246
+ validate: (value, _control) => {
247
247
  if (value === null || value === undefined) {
248
248
  return { valid: true };
249
249
  }
@@ -319,7 +319,6 @@ function validateField(value, control) {
319
319
  return validateSelect(value, control);
320
320
  case "file":
321
321
  return validateFile(value, control);
322
- case "text":
323
322
  default:
324
323
  return validateText(value, control);
325
324
  }
@@ -370,7 +369,7 @@ function validateEmail(value, control) {
370
369
  }
371
370
  function validateNumber(value, control) {
372
371
  const numValue = typeof value === "number" ? value : parseFloat(String(value).replace(/[,$]/g, ""));
373
- if (isNaN(numValue)) {
372
+ if (Number.isNaN(numValue)) {
374
373
  return {
375
374
  valid: false,
376
375
  error: `${control.label || control.key} must be a number`
@@ -414,7 +413,7 @@ function validateDate(value, control) {
414
413
  error: `${control.label || control.key} must be a valid date`
415
414
  };
416
415
  }
417
- if (isNaN(dateValue.getTime())) {
416
+ if (Number.isNaN(dateValue.getTime())) {
418
417
  return {
419
418
  valid: false,
420
419
  error: `${control.label || control.key} must be a valid date`
@@ -515,9 +514,6 @@ function parseValue(value, control) {
515
514
  const timestamp = Date.parse(value);
516
515
  return Number.isFinite(timestamp) ? new Date(timestamp).toISOString() : value;
517
516
  }
518
- case "text":
519
- case "email":
520
- case "select":
521
517
  default:
522
518
  return value;
523
519
  }
@@ -616,20 +612,13 @@ function isLifecycleIntent(intent) {
616
612
  return ["submit", "stash", "restore", "cancel"].includes(intent);
617
613
  }
618
614
  function isUXIntent(intent) {
619
- return [
620
- "undo",
621
- "skip",
622
- "explain",
623
- "example",
624
- "progress",
625
- "autofill"
626
- ].includes(intent);
615
+ return ["undo", "skip", "explain", "example", "progress", "autofill"].includes(intent);
627
616
  }
628
617
  function hasDataToExtract(intent) {
629
618
  return intent === "fill_form" || intent === "other";
630
619
  }
631
620
 
632
- // ../../../../node_modules/uuid/dist/esm/stringify.js
621
+ // node_modules/uuid/dist-node/stringify.js
633
622
  function unsafeStringify(arr, offset = 0) {
634
623
  return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
635
624
  }
@@ -641,8 +630,8 @@ var init_stringify = __esm(() => {
641
630
  }
642
631
  });
643
632
 
644
- // ../../../../node_modules/uuid/dist/esm/rng.js
645
- import { randomFillSync } from "crypto";
633
+ // node_modules/uuid/dist-node/rng.js
634
+ import { randomFillSync } from "node:crypto";
646
635
  function rng() {
647
636
  if (poolPtr > rnds8Pool.length - 16) {
648
637
  randomFillSync(rnds8Pool);
@@ -656,18 +645,15 @@ var init_rng = __esm(() => {
656
645
  poolPtr = rnds8Pool.length;
657
646
  });
658
647
 
659
- // ../../../../node_modules/uuid/dist/esm/native.js
660
- import { randomUUID } from "crypto";
648
+ // node_modules/uuid/dist-node/native.js
649
+ import { randomUUID } from "node:crypto";
661
650
  var native_default;
662
651
  var init_native = __esm(() => {
663
652
  native_default = { randomUUID };
664
653
  });
665
654
 
666
- // ../../../../node_modules/uuid/dist/esm/v4.js
667
- function v4(options, buf, offset) {
668
- if (native_default.randomUUID && !buf && !options) {
669
- return native_default.randomUUID();
670
- }
655
+ // node_modules/uuid/dist-node/v4.js
656
+ function _v4(options, buf, offset) {
671
657
  options = options || {};
672
658
  const rnds = options.random ?? options.rng?.() ?? rng();
673
659
  if (rnds.length < 16) {
@@ -687,6 +673,12 @@ function v4(options, buf, offset) {
687
673
  }
688
674
  return unsafeStringify(rnds);
689
675
  }
676
+ function v4(options, buf, offset) {
677
+ if (native_default.randomUUID && !buf && !options) {
678
+ return native_default.randomUUID();
679
+ }
680
+ return _v4(options, buf, offset);
681
+ }
690
682
  var v4_default;
691
683
  var init_v4 = __esm(() => {
692
684
  init_native();
@@ -695,8 +687,8 @@ var init_v4 = __esm(() => {
695
687
  v4_default = v4;
696
688
  });
697
689
 
698
- // ../../../../node_modules/uuid/dist/esm/index.js
699
- var init_esm = __esm(() => {
690
+ // node_modules/uuid/dist-node/index.js
691
+ var init_dist_node = __esm(() => {
700
692
  init_v4();
701
693
  });
702
694
 
@@ -868,7 +860,7 @@ var isRecord = (value) => typeof value === "object" && value !== null && !Array.
868
860
  return typeof data.formId === "string" && typeof data.updatedAt === "number" && typeof data.values === "object";
869
861
  };
870
862
  var init_storage = __esm(() => {
871
- init_esm();
863
+ init_dist_node();
872
864
  init_types();
873
865
  });
874
866
 
@@ -1037,7 +1029,7 @@ function parseExtractionResponse(response) {
1037
1029
  }
1038
1030
  }
1039
1031
  }
1040
- } catch (error) {
1032
+ } catch (_error) {
1041
1033
  const intentMatch = response.match(/<intent>([^<]+)<\/intent>/);
1042
1034
  if (intentMatch) {
1043
1035
  const intentStr = intentMatch[1].toLowerCase().trim();
@@ -1187,9 +1179,8 @@ Respond in XML:
1187
1179
  }
1188
1180
  }
1189
1181
  var init_extraction = __esm(() => {
1190
- init_validation();
1191
- init_validation();
1192
1182
  init_template();
1183
+ init_validation();
1193
1184
  });
1194
1185
 
1195
1186
  // src/service.ts
@@ -1198,19 +1189,19 @@ __export(exports_service, {
1198
1189
  FormService: () => FormService
1199
1190
  });
1200
1191
  import {
1201
- Service,
1202
- logger
1192
+ logger,
1193
+ Service
1203
1194
  } from "@elizaos/core";
1204
1195
  function prettify3(key) {
1205
1196
  return key.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
1206
1197
  }
1207
1198
  var FormService;
1208
1199
  var init_service = __esm(() => {
1209
- init_esm();
1210
- init_types();
1200
+ init_dist_node();
1201
+ init_builtins();
1211
1202
  init_storage();
1203
+ init_types();
1212
1204
  init_validation();
1213
- init_builtins();
1214
1205
  FormService = class FormService extends Service {
1215
1206
  static serviceType = "FORM";
1216
1207
  capabilityDescription = "Manages conversational forms for data collection";
@@ -1251,6 +1242,13 @@ var init_service = __esm(() => {
1251
1242
  listForms() {
1252
1243
  return Array.from(this.forms.values());
1253
1244
  }
1245
+ registerType(type, handler) {
1246
+ registerTypeHandler(type, handler);
1247
+ logger.debug(`[FormService] Registered type handler: ${type}`);
1248
+ }
1249
+ getTypeHandler(type) {
1250
+ return getTypeHandler(type);
1251
+ }
1254
1252
  registerControlType(type, options) {
1255
1253
  const existing = this.controlTypes.get(type.id);
1256
1254
  if (existing) {
@@ -1679,7 +1677,7 @@ var init_service = __esm(() => {
1679
1677
  value,
1680
1678
  externalData
1681
1679
  });
1682
- } catch (error) {
1680
+ } catch (_error) {
1683
1681
  logger.debug(`[FormService] No event handler for FORM_FIELD_CONFIRMED`);
1684
1682
  }
1685
1683
  logger.info(`[FormService] Confirmed external field ${field}`);
@@ -1722,7 +1720,7 @@ var init_service = __esm(() => {
1722
1720
  field,
1723
1721
  reason
1724
1722
  });
1725
- } catch (error) {
1723
+ } catch (_error) {
1726
1724
  logger.debug(`[FormService] No event handler for FORM_FIELD_CANCELLED`);
1727
1725
  }
1728
1726
  logger.info(`[FormService] Cancelled external field ${field}: ${reason}`);
@@ -2039,218 +2037,159 @@ var init_service = __esm(() => {
2039
2037
  };
2040
2038
  });
2041
2039
 
2042
- // src/providers/context.ts
2043
- var exports_context = {};
2044
- __export(exports_context, {
2045
- formContextProvider: () => formContextProvider,
2046
- default: () => context_default
2040
+ // src/actions/restore.ts
2041
+ var exports_restore = {};
2042
+ __export(exports_restore, {
2043
+ formRestoreAction: () => formRestoreAction,
2044
+ default: () => restore_default
2047
2045
  });
2048
- import { logger as logger2 } from "@elizaos/core";
2049
- var formContextProvider, context_default;
2050
- var init_context = __esm(() => {
2051
- init_template();
2052
- formContextProvider = {
2053
- name: "FORM_CONTEXT",
2054
- description: "Provides context about active form sessions",
2055
- get: async (runtime, message, _state) => {
2046
+ import {
2047
+ logger as logger2
2048
+ } from "@elizaos/core";
2049
+ var formRestoreAction, restore_default;
2050
+ var init_restore = __esm(() => {
2051
+ formRestoreAction = {
2052
+ name: "FORM_RESTORE",
2053
+ similes: ["RESUME_FORM", "CONTINUE_FORM"],
2054
+ description: "Restore a previously stashed form session",
2055
+ validate: async (runtime, message, _state) => {
2056
2056
  try {
2057
+ const text = message.content?.text || "";
2058
+ const intent = quickIntentDetect(text);
2059
+ if (intent !== "restore") {
2060
+ return false;
2061
+ }
2057
2062
  const formService = runtime.getService("FORM");
2058
2063
  if (!formService) {
2059
- return {
2060
- data: { hasActiveForm: false },
2061
- values: { formContext: "" },
2062
- text: ""
2063
- };
2064
+ return false;
2065
+ }
2066
+ const entityId = message.entityId;
2067
+ if (!entityId)
2068
+ return false;
2069
+ const stashed = await formService.getStashedSessions(entityId);
2070
+ return stashed.length > 0;
2071
+ } catch (error) {
2072
+ logger2.error("[FormRestoreAction] Validation error:", String(error));
2073
+ return false;
2074
+ }
2075
+ },
2076
+ handler: async (runtime, message, _state, _options, callback) => {
2077
+ try {
2078
+ const formService = runtime.getService("FORM");
2079
+ if (!formService) {
2080
+ await callback?.({
2081
+ text: "Sorry, I couldn't find the form service."
2082
+ });
2083
+ return { success: false };
2064
2084
  }
2065
2085
  const entityId = message.entityId;
2066
2086
  const roomId = message.roomId;
2067
2087
  if (!entityId || !roomId) {
2068
- return {
2069
- data: { hasActiveForm: false },
2070
- values: { formContext: "" },
2071
- text: ""
2072
- };
2088
+ await callback?.({
2089
+ text: "Sorry, I couldn't identify you."
2090
+ });
2091
+ return { success: false };
2092
+ }
2093
+ const existing = await formService.getActiveSession(entityId, roomId);
2094
+ if (existing) {
2095
+ const form2 = formService.getForm(existing.formId);
2096
+ await callback?.({
2097
+ text: `You already have an active form: "${form2?.name || existing.formId}". Would you like to continue with that one, or should I save it and restore your other form?`
2098
+ });
2099
+ return { success: false };
2073
2100
  }
2074
- const session = await formService.getActiveSession(entityId, roomId);
2075
2101
  const stashed = await formService.getStashedSessions(entityId);
2076
- if (!session && stashed.length === 0) {
2077
- return {
2078
- data: { hasActiveForm: false, stashedCount: 0 },
2079
- values: { formContext: "" },
2080
- text: ""
2081
- };
2102
+ if (stashed.length === 0) {
2103
+ await callback?.({
2104
+ text: "You don't have any saved forms to resume."
2105
+ });
2106
+ return { success: false };
2082
2107
  }
2083
- let contextText = "";
2084
- let contextState;
2085
- if (session) {
2086
- contextState = formService.getSessionContext(session);
2087
- const form = formService.getForm(session.formId);
2088
- const templateValues = buildTemplateValues(session);
2089
- const resolveText = (value) => renderTemplate(value, templateValues);
2090
- contextState = {
2091
- ...contextState,
2092
- filledFields: contextState.filledFields.map((field) => ({
2093
- ...field,
2094
- label: resolveText(field.label) ?? field.label
2095
- })),
2096
- missingRequired: contextState.missingRequired.map((field) => ({
2097
- ...field,
2098
- label: resolveText(field.label) ?? field.label,
2099
- description: resolveText(field.description),
2100
- askPrompt: resolveText(field.askPrompt)
2101
- })),
2102
- uncertainFields: contextState.uncertainFields.map((field) => ({
2103
- ...field,
2104
- label: resolveText(field.label) ?? field.label
2105
- })),
2106
- nextField: contextState.nextField ? resolveControlTemplates(contextState.nextField, templateValues) : null
2107
- };
2108
- contextText = `# Active Form: ${form?.name || session.formId}
2109
-
2110
- `;
2111
- contextText += `Progress: ${contextState.progress}%
2108
+ const sessionToRestore = stashed.sort((a, b) => b.updatedAt - a.updatedAt)[0];
2109
+ const session = await formService.restore(sessionToRestore.id, entityId);
2110
+ const form = formService.getForm(session.formId);
2111
+ const context = formService.getSessionContext(session);
2112
+ let responseText = `I've restored your "${form?.name || session.formId}" form. `;
2113
+ responseText += `You're ${context.progress}% complete. `;
2114
+ if (context.filledFields.length > 0) {
2115
+ responseText += `
2112
2116
 
2117
+ Here's what I have so far:
2113
2118
  `;
2114
- if (contextState.filledFields.length > 0) {
2115
- contextText += `## Collected Information
2116
- `;
2117
- for (const field of contextState.filledFields) {
2118
- contextText += `- ${field.label}: ${field.displayValue}
2119
- `;
2120
- }
2121
- contextText += `
2119
+ for (const field of context.filledFields) {
2120
+ responseText += `• ${field.label}: ${field.displayValue}
2122
2121
  `;
2123
2122
  }
2124
- if (contextState.missingRequired.length > 0) {
2125
- contextText += `## Still Needed
2126
- `;
2127
- for (const field of contextState.missingRequired) {
2128
- contextText += `- ${field.label}${field.description ? ` (${field.description})` : ""}
2129
- `;
2130
- }
2131
- contextText += `
2132
- `;
2123
+ }
2124
+ if (context.nextField) {
2125
+ responseText += `
2126
+ Let's continue with ${context.nextField.label}.`;
2127
+ if (context.nextField.askPrompt) {
2128
+ responseText += ` ${context.nextField.askPrompt}`;
2133
2129
  }
2134
- if (contextState.uncertainFields.length > 0) {
2135
- contextText += `## Needs Confirmation
2136
- `;
2137
- for (const field of contextState.uncertainFields) {
2138
- contextText += `- ${field.label}: "${field.value}" (${Math.round(field.confidence * 100)}% confident)
2139
- `;
2140
- }
2141
- contextText += `
2142
- `;
2130
+ } else if (context.status === "ready") {
2131
+ responseText += `
2132
+ Everything looks complete! Ready to submit?`;
2133
+ }
2134
+ await callback?.({
2135
+ text: responseText
2136
+ });
2137
+ return {
2138
+ success: true,
2139
+ data: {
2140
+ sessionId: session.id,
2141
+ formId: session.formId,
2142
+ progress: context.progress
2143
2143
  }
2144
- if (contextState.pendingExternalFields.length > 0) {
2145
- contextText += `## Waiting For External Action
2146
- `;
2147
- for (const field of contextState.pendingExternalFields) {
2148
- const ageMs = Date.now() - field.activatedAt;
2149
- const ageMin = Math.floor(ageMs / 60000);
2150
- const ageText = ageMin < 1 ? "just now" : `${ageMin}m ago`;
2151
- contextText += `- ${field.label}: ${field.instructions} (started ${ageText})
2152
- `;
2153
- if (field.address) {
2154
- contextText += ` Address: ${field.address}
2155
- `;
2156
- }
2157
- }
2158
- contextText += `
2159
- `;
2144
+ };
2145
+ } catch (error) {
2146
+ logger2.error("[FormRestoreAction] Handler error:", String(error));
2147
+ await callback?.({
2148
+ text: "Sorry, I couldn't restore your form. Please try again."
2149
+ });
2150
+ return { success: false };
2151
+ }
2152
+ },
2153
+ examples: [
2154
+ [
2155
+ {
2156
+ name: "{{user1}}",
2157
+ content: { text: "Resume my form" }
2158
+ },
2159
+ {
2160
+ name: "{{agentName}}",
2161
+ content: {
2162
+ text: "I've restored your form. Let's continue where you left off."
2160
2163
  }
2161
- contextText += `## Agent Guidance
2162
- `;
2163
- if (contextState.pendingExternalFields.length > 0) {
2164
- const pending = contextState.pendingExternalFields[0];
2165
- contextText += `Waiting for external action. Remind user: "${pending.instructions}"
2166
- `;
2167
- } else if (contextState.pendingCancelConfirmation) {
2168
- contextText += `User is trying to cancel. Confirm: "You've spent time on this. Are you sure you want to cancel?"
2169
- `;
2170
- } else if (contextState.uncertainFields.length > 0) {
2171
- const uncertain = contextState.uncertainFields[0];
2172
- contextText += `Ask user to confirm: "I understood your ${uncertain.label} as '${uncertain.value}'. Is that correct?"
2173
- `;
2174
- } else if (contextState.nextField) {
2175
- const next = contextState.nextField;
2176
- const prompt = next.askPrompt || `Ask for their ${next.label}`;
2177
- contextText += `Next: ${prompt}
2178
- `;
2179
- if (next.example) {
2180
- contextText += `Example: "${next.example}"
2181
- `;
2182
- }
2183
- } else if (contextState.status === "ready") {
2184
- contextText += `All fields collected! Nudge user to submit: "I have everything I need. Ready to submit?"
2185
- `;
2164
+ }
2165
+ ],
2166
+ [
2167
+ {
2168
+ name: "{{user1}}",
2169
+ content: { text: "Continue with my registration" }
2170
+ },
2171
+ {
2172
+ name: "{{agentName}}",
2173
+ content: {
2174
+ text: "I've restored your Registration form. You're 60% complete."
2186
2175
  }
2187
- contextText += `
2188
- `;
2189
- contextText += `## User Can Say
2190
- `;
2191
- contextText += `- Provide information for any field
2192
- `;
2193
- contextText += `- "undo" or "go back" to revert last change
2194
- `;
2195
- contextText += `- "skip" to skip optional fields
2196
- `;
2197
- contextText += `- "why?" to get explanation about a field
2198
- `;
2199
- contextText += `- "how far?" to check progress
2200
- `;
2201
- contextText += `- "submit" or "done" when ready
2202
- `;
2203
- contextText += `- "save for later" to stash the form
2204
- `;
2205
- contextText += `- "cancel" to abandon the form
2206
- `;
2207
- } else {
2208
- contextState = {
2209
- hasActiveForm: false,
2210
- progress: 0,
2211
- filledFields: [],
2212
- missingRequired: [],
2213
- uncertainFields: [],
2214
- nextField: null,
2215
- stashedCount: stashed.length,
2216
- pendingExternalFields: []
2217
- };
2218
2176
  }
2219
- if (stashed.length > 0) {
2220
- contextText += `
2221
- ## Saved Forms
2222
- `;
2223
- contextText += `User has ${stashed.length} saved form(s). They can say "resume" or "continue" to restore one.
2224
- `;
2225
- for (const s of stashed) {
2226
- const form = formService.getForm(s.formId);
2227
- const ctx = formService.getSessionContext(s);
2228
- contextText += `- ${form?.name || s.formId} (${ctx.progress}% complete)
2229
- `;
2177
+ ],
2178
+ [
2179
+ {
2180
+ name: "{{user1}}",
2181
+ content: { text: "Pick up where I left off" }
2182
+ },
2183
+ {
2184
+ name: "{{agentName}}",
2185
+ content: {
2186
+ text: "I've restored your form. Here's what you have so far..."
2230
2187
  }
2231
2188
  }
2232
- return {
2233
- data: JSON.parse(JSON.stringify(contextState)),
2234
- values: {
2235
- formContext: contextText,
2236
- hasActiveForm: String(contextState.hasActiveForm),
2237
- formProgress: String(contextState.progress),
2238
- formStatus: contextState.status || "",
2239
- stashedCount: String(stashed.length)
2240
- },
2241
- text: contextText
2242
- };
2243
- } catch (error) {
2244
- logger2.error("[FormContextProvider] Error:", String(error));
2245
- return {
2246
- data: { hasActiveForm: false, error: true },
2247
- values: { formContext: "Error loading form context." },
2248
- text: "Error loading form context."
2249
- };
2250
- }
2251
- }
2189
+ ]
2190
+ ]
2252
2191
  };
2253
- context_default = formContextProvider;
2192
+ restore_default = formRestoreAction;
2254
2193
  });
2255
2194
 
2256
2195
  // src/evaluators/extractor.ts
@@ -2387,7 +2326,7 @@ var init_extractor = __esm(() => {
2387
2326
  return false;
2388
2327
  }
2389
2328
  },
2390
- handler: async (runtime, message, state) => {
2329
+ handler: async (runtime, message, _state) => {
2391
2330
  try {
2392
2331
  const formService = runtime.getService("FORM");
2393
2332
  if (!formService)
@@ -2450,7 +2389,6 @@ var init_extractor = __esm(() => {
2450
2389
  case "restore":
2451
2390
  logger3.debug("[FormEvaluator] Restore intent - deferring to action");
2452
2391
  break;
2453
- case "fill_form":
2454
2392
  default:
2455
2393
  await processExtractions(runtime, formService, session, form, entityId, extractions, message.id);
2456
2394
  break;
@@ -2470,165 +2408,223 @@ var init_extractor = __esm(() => {
2470
2408
  extractor_default = formEvaluator;
2471
2409
  });
2472
2410
 
2473
- // src/actions/restore.ts
2474
- var exports_restore = {};
2475
- __export(exports_restore, {
2476
- formRestoreAction: () => formRestoreAction,
2477
- default: () => restore_default
2411
+ // src/providers/context.ts
2412
+ var exports_context = {};
2413
+ __export(exports_context, {
2414
+ formContextProvider: () => formContextProvider,
2415
+ default: () => context_default
2478
2416
  });
2479
- import {
2480
- logger as logger4
2481
- } from "@elizaos/core";
2482
- var formRestoreAction, restore_default;
2483
- var init_restore = __esm(() => {
2484
- formRestoreAction = {
2485
- name: "FORM_RESTORE",
2486
- similes: ["RESUME_FORM", "CONTINUE_FORM"],
2487
- description: "Restore a previously stashed form session",
2488
- validate: async (runtime, message, _state) => {
2489
- try {
2490
- const text = message.content?.text || "";
2491
- const intent = quickIntentDetect(text);
2492
- if (intent !== "restore") {
2493
- return false;
2494
- }
2495
- const formService = runtime.getService("FORM");
2496
- if (!formService) {
2497
- return false;
2498
- }
2499
- const entityId = message.entityId;
2500
- if (!entityId)
2501
- return false;
2502
- const stashed = await formService.getStashedSessions(entityId);
2503
- return stashed.length > 0;
2504
- } catch (error) {
2505
- logger4.error("[FormRestoreAction] Validation error:", String(error));
2506
- return false;
2507
- }
2508
- },
2509
- handler: async (runtime, message, _state, _options, callback) => {
2417
+ import { logger as logger4 } from "@elizaos/core";
2418
+ var formContextProvider, context_default;
2419
+ var init_context = __esm(() => {
2420
+ init_template();
2421
+ formContextProvider = {
2422
+ name: "FORM_CONTEXT",
2423
+ description: "Provides context about active form sessions",
2424
+ get: async (runtime, message, _state) => {
2510
2425
  try {
2511
2426
  const formService = runtime.getService("FORM");
2512
2427
  if (!formService) {
2513
- await callback?.({
2514
- text: "Sorry, I couldn't find the form service."
2515
- });
2516
- return { success: false };
2428
+ return {
2429
+ data: { hasActiveForm: false },
2430
+ values: { formContext: "" },
2431
+ text: ""
2432
+ };
2517
2433
  }
2518
2434
  const entityId = message.entityId;
2519
2435
  const roomId = message.roomId;
2520
2436
  if (!entityId || !roomId) {
2521
- await callback?.({
2522
- text: "Sorry, I couldn't identify you."
2523
- });
2524
- return { success: false };
2525
- }
2526
- const existing = await formService.getActiveSession(entityId, roomId);
2527
- if (existing) {
2528
- const form2 = formService.getForm(existing.formId);
2529
- await callback?.({
2530
- text: `You already have an active form: "${form2?.name || existing.formId}". Would you like to continue with that one, or should I save it and restore your other form?`
2531
- });
2532
- return { success: false };
2437
+ return {
2438
+ data: { hasActiveForm: false },
2439
+ values: { formContext: "" },
2440
+ text: ""
2441
+ };
2533
2442
  }
2443
+ const session = await formService.getActiveSession(entityId, roomId);
2534
2444
  const stashed = await formService.getStashedSessions(entityId);
2535
- if (stashed.length === 0) {
2536
- await callback?.({
2537
- text: "You don't have any saved forms to resume."
2538
- });
2539
- return { success: false };
2445
+ if (!session && stashed.length === 0) {
2446
+ return {
2447
+ data: { hasActiveForm: false, stashedCount: 0 },
2448
+ values: { formContext: "" },
2449
+ text: ""
2450
+ };
2540
2451
  }
2541
- const sessionToRestore = stashed.sort((a, b) => b.updatedAt - a.updatedAt)[0];
2542
- const session = await formService.restore(sessionToRestore.id, entityId);
2543
- const form = formService.getForm(session.formId);
2544
- const context = formService.getSessionContext(session);
2545
- let responseText = `I've restored your "${form?.name || session.formId}" form. `;
2546
- responseText += `You're ${context.progress}% complete. `;
2547
- if (context.filledFields.length > 0) {
2548
- responseText += `
2452
+ let contextText = "";
2453
+ let contextState;
2454
+ if (session) {
2455
+ contextState = formService.getSessionContext(session);
2456
+ const form = formService.getForm(session.formId);
2457
+ const templateValues = buildTemplateValues(session);
2458
+ const resolveText = (value) => renderTemplate(value, templateValues);
2459
+ contextState = {
2460
+ ...contextState,
2461
+ filledFields: contextState.filledFields.map((field) => ({
2462
+ ...field,
2463
+ label: resolveText(field.label) ?? field.label
2464
+ })),
2465
+ missingRequired: contextState.missingRequired.map((field) => ({
2466
+ ...field,
2467
+ label: resolveText(field.label) ?? field.label,
2468
+ description: resolveText(field.description),
2469
+ askPrompt: resolveText(field.askPrompt)
2470
+ })),
2471
+ uncertainFields: contextState.uncertainFields.map((field) => ({
2472
+ ...field,
2473
+ label: resolveText(field.label) ?? field.label
2474
+ })),
2475
+ nextField: contextState.nextField ? resolveControlTemplates(contextState.nextField, templateValues) : null
2476
+ };
2477
+ contextText = `# Active Form: ${form?.name || session.formId}
2549
2478
 
2550
- Here's what I have so far:
2551
2479
  `;
2552
- for (const field of context.filledFields) {
2553
- responseText += `• ${field.label}: ${field.displayValue}
2480
+ contextText += `Progress: ${contextState.progress}%
2481
+
2482
+ `;
2483
+ if (contextState.filledFields.length > 0) {
2484
+ contextText += `## Collected Information
2485
+ `;
2486
+ for (const field of contextState.filledFields) {
2487
+ contextText += `- ${field.label}: ${field.displayValue}
2488
+ `;
2489
+ }
2490
+ contextText += `
2554
2491
  `;
2555
2492
  }
2556
- }
2557
- if (context.nextField) {
2558
- responseText += `
2559
- Let's continue with ${context.nextField.label}.`;
2560
- if (context.nextField.askPrompt) {
2561
- responseText += ` ${context.nextField.askPrompt}`;
2493
+ if (contextState.missingRequired.length > 0) {
2494
+ contextText += `## Still Needed
2495
+ `;
2496
+ for (const field of contextState.missingRequired) {
2497
+ contextText += `- ${field.label}${field.description ? ` (${field.description})` : ""}
2498
+ `;
2499
+ }
2500
+ contextText += `
2501
+ `;
2562
2502
  }
2563
- } else if (context.status === "ready") {
2564
- responseText += `
2565
- Everything looks complete! Ready to submit?`;
2566
- }
2567
- await callback?.({
2568
- text: responseText
2569
- });
2570
- return {
2571
- success: true,
2572
- data: {
2573
- sessionId: session.id,
2574
- formId: session.formId,
2575
- progress: context.progress
2503
+ if (contextState.uncertainFields.length > 0) {
2504
+ contextText += `## Needs Confirmation
2505
+ `;
2506
+ for (const field of contextState.uncertainFields) {
2507
+ contextText += `- ${field.label}: "${field.value}" (${Math.round(field.confidence * 100)}% confident)
2508
+ `;
2509
+ }
2510
+ contextText += `
2511
+ `;
2576
2512
  }
2577
- };
2578
- } catch (error) {
2579
- logger4.error("[FormRestoreAction] Handler error:", String(error));
2580
- await callback?.({
2581
- text: "Sorry, I couldn't restore your form. Please try again."
2582
- });
2583
- return { success: false };
2584
- }
2585
- },
2586
- examples: [
2587
- [
2588
- {
2589
- name: "{{user1}}",
2590
- content: { text: "Resume my form" }
2591
- },
2592
- {
2593
- name: "{{agentName}}",
2594
- content: {
2595
- text: "I've restored your form. Let's continue where you left off."
2513
+ if (contextState.pendingExternalFields.length > 0) {
2514
+ contextText += `## Waiting For External Action
2515
+ `;
2516
+ for (const field of contextState.pendingExternalFields) {
2517
+ const ageMs = Date.now() - field.activatedAt;
2518
+ const ageMin = Math.floor(ageMs / 60000);
2519
+ const ageText = ageMin < 1 ? "just now" : `${ageMin}m ago`;
2520
+ contextText += `- ${field.label}: ${field.instructions} (started ${ageText})
2521
+ `;
2522
+ if (field.address) {
2523
+ contextText += ` Address: ${field.address}
2524
+ `;
2525
+ }
2526
+ }
2527
+ contextText += `
2528
+ `;
2596
2529
  }
2597
- }
2598
- ],
2599
- [
2600
- {
2601
- name: "{{user1}}",
2602
- content: { text: "Continue with my registration" }
2603
- },
2604
- {
2605
- name: "{{agentName}}",
2606
- content: {
2607
- text: "I've restored your Registration form. You're 60% complete."
2530
+ contextText += `## Agent Guidance
2531
+ `;
2532
+ if (contextState.pendingExternalFields.length > 0) {
2533
+ const pending = contextState.pendingExternalFields[0];
2534
+ contextText += `Waiting for external action. Remind user: "${pending.instructions}"
2535
+ `;
2536
+ } else if (contextState.pendingCancelConfirmation) {
2537
+ contextText += `User is trying to cancel. Confirm: "You've spent time on this. Are you sure you want to cancel?"
2538
+ `;
2539
+ } else if (contextState.uncertainFields.length > 0) {
2540
+ const uncertain = contextState.uncertainFields[0];
2541
+ contextText += `Ask user to confirm: "I understood your ${uncertain.label} as '${uncertain.value}'. Is that correct?"
2542
+ `;
2543
+ } else if (contextState.nextField) {
2544
+ const next = contextState.nextField;
2545
+ const prompt = next.askPrompt || `Ask for their ${next.label}`;
2546
+ contextText += `Next: ${prompt}
2547
+ `;
2548
+ if (next.example) {
2549
+ contextText += `Example: "${next.example}"
2550
+ `;
2551
+ }
2552
+ } else if (contextState.status === "ready") {
2553
+ contextText += `All fields collected! Nudge user to submit: "I have everything I need. Ready to submit?"
2554
+ `;
2608
2555
  }
2556
+ contextText += `
2557
+ `;
2558
+ contextText += `## User Can Say
2559
+ `;
2560
+ contextText += `- Provide information for any field
2561
+ `;
2562
+ contextText += `- "undo" or "go back" to revert last change
2563
+ `;
2564
+ contextText += `- "skip" to skip optional fields
2565
+ `;
2566
+ contextText += `- "why?" to get explanation about a field
2567
+ `;
2568
+ contextText += `- "how far?" to check progress
2569
+ `;
2570
+ contextText += `- "submit" or "done" when ready
2571
+ `;
2572
+ contextText += `- "save for later" to stash the form
2573
+ `;
2574
+ contextText += `- "cancel" to abandon the form
2575
+ `;
2576
+ } else {
2577
+ contextState = {
2578
+ hasActiveForm: false,
2579
+ progress: 0,
2580
+ filledFields: [],
2581
+ missingRequired: [],
2582
+ uncertainFields: [],
2583
+ nextField: null,
2584
+ stashedCount: stashed.length,
2585
+ pendingExternalFields: []
2586
+ };
2609
2587
  }
2610
- ],
2611
- [
2612
- {
2613
- name: "{{user1}}",
2614
- content: { text: "Pick up where I left off" }
2615
- },
2616
- {
2617
- name: "{{agentName}}",
2618
- content: {
2619
- text: "I've restored your form. Here's what you have so far..."
2588
+ if (stashed.length > 0) {
2589
+ contextText += `
2590
+ ## Saved Forms
2591
+ `;
2592
+ contextText += `User has ${stashed.length} saved form(s). They can say "resume" or "continue" to restore one.
2593
+ `;
2594
+ for (const s of stashed) {
2595
+ const form = formService.getForm(s.formId);
2596
+ const ctx = formService.getSessionContext(s);
2597
+ contextText += `- ${form?.name || s.formId} (${ctx.progress}% complete)
2598
+ `;
2620
2599
  }
2621
2600
  }
2622
- ]
2623
- ]
2601
+ return {
2602
+ data: JSON.parse(JSON.stringify(contextState)),
2603
+ values: {
2604
+ formContext: contextText,
2605
+ hasActiveForm: String(contextState.hasActiveForm),
2606
+ formProgress: String(contextState.progress),
2607
+ formStatus: contextState.status || "",
2608
+ stashedCount: String(stashed.length)
2609
+ },
2610
+ text: contextText
2611
+ };
2612
+ } catch (error) {
2613
+ logger4.error("[FormContextProvider] Error:", String(error));
2614
+ return {
2615
+ data: { hasActiveForm: false, error: true },
2616
+ values: { formContext: "Error loading form context." },
2617
+ text: "Error loading form context."
2618
+ };
2619
+ }
2620
+ }
2624
2621
  };
2625
- restore_default = formRestoreAction;
2622
+ context_default = formContextProvider;
2626
2623
  });
2627
2624
 
2628
- // src/index.ts
2625
+ // index.ts
2629
2626
  init_builtins();
2630
2627
  init_validation();
2631
- init_validation();
2632
2628
  init_storage();
2633
2629
  init_extraction();
2634
2630
  init_types();
@@ -2953,7 +2949,7 @@ class FormBuilder {
2953
2949
  }
2954
2950
  control(builder) {
2955
2951
  const ctrl = builder instanceof ControlBuilder ? builder.build() : builder;
2956
- this.form.controls.push(ctrl);
2952
+ this.form.controls?.push(ctrl);
2957
2953
  return this;
2958
2954
  }
2959
2955
  controls(...builders) {
@@ -3070,11 +3066,102 @@ function prettify2(key) {
3070
3066
  return key.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
3071
3067
  }
3072
3068
 
3073
- // src/index.ts
3069
+ // index.ts
3074
3070
  init_service();
3075
- init_context();
3076
- init_extractor();
3077
3071
  init_restore();
3072
+ init_extractor();
3073
+ init_context();
3074
+
3075
+ // src/tasks/nudge.ts
3076
+ import { logger as logger5 } from "@elizaos/core";
3077
+ var formNudgeWorker = {
3078
+ name: "form_nudge_check",
3079
+ validate: async (_runtime, _message, _state) => {
3080
+ return true;
3081
+ },
3082
+ execute: async (runtime, _options, _task) => {
3083
+ try {
3084
+ const formService = runtime.getService("FORM");
3085
+ if (!formService) {
3086
+ logger5.debug("[FormNudge] Form service not available");
3087
+ return;
3088
+ }
3089
+ logger5.debug("[FormNudge] Nudge check cycle completed");
3090
+ } catch (error) {
3091
+ logger5.error("[FormNudge] Error during nudge check:", String(error));
3092
+ }
3093
+ }
3094
+ };
3095
+ async function processEntityNudges(runtime, entityId) {
3096
+ const formService = runtime.getService("FORM");
3097
+ if (!formService)
3098
+ return;
3099
+ const activeSessions = await formService.getAllActiveSessions(entityId);
3100
+ const stashedSessions = await formService.getStashedSessions(entityId);
3101
+ const allSessions = [...activeSessions, ...stashedSessions];
3102
+ const now = Date.now();
3103
+ const expirationWarningMs = 24 * 60 * 60 * 1000;
3104
+ for (const session of allSessions) {
3105
+ const form = formService.getForm(session.formId);
3106
+ if (session.expiresAt < now) {
3107
+ session.status = "expired";
3108
+ await formService.saveSession(session);
3109
+ if (form?.hooks?.onExpire) {
3110
+ const worker = runtime.getTaskWorker(form.hooks.onExpire);
3111
+ if (worker) {
3112
+ try {
3113
+ await worker.execute(runtime, { session, form }, {});
3114
+ } catch (error) {
3115
+ logger5.error("[FormNudge] onExpire hook failed:", String(error));
3116
+ }
3117
+ }
3118
+ }
3119
+ logger5.debug(`[FormNudge] Session ${session.id} expired`);
3120
+ continue;
3121
+ }
3122
+ if (isExpiringSoon(session, expirationWarningMs) && !session.expirationWarned) {
3123
+ await sendExpirationWarning(runtime, session, form);
3124
+ session.expirationWarned = true;
3125
+ await formService.saveSession(session);
3126
+ continue;
3127
+ }
3128
+ if (session.status === "stashed" && shouldNudge(session, form)) {
3129
+ await sendNudge(runtime, session, form);
3130
+ session.nudgeCount = (session.nudgeCount || 0) + 1;
3131
+ session.lastNudgeAt = now;
3132
+ await formService.saveSession(session);
3133
+ }
3134
+ }
3135
+ }
3136
+ async function sendNudge(runtime, session, form) {
3137
+ const message = form?.nudge?.message || `You have an unfinished "${form?.name || "form"}". Would you like to continue?`;
3138
+ try {
3139
+ if (typeof runtime.sendMessageToRoom === "function") {
3140
+ await runtime.sendMessageToRoom(session.roomId, {
3141
+ text: message
3142
+ });
3143
+ logger5.debug(`[FormNudge] Sent nudge for session in room ${session.roomId}`);
3144
+ }
3145
+ } catch (error) {
3146
+ logger5.error("[FormNudge] Failed to send nudge:", String(error));
3147
+ }
3148
+ }
3149
+ async function sendExpirationWarning(runtime, session, form) {
3150
+ const remaining = formatTimeRemaining(session);
3151
+ const message = `Your "${form?.name || "form"}" form will expire in ${remaining}. Say "resume" to keep working on it.`;
3152
+ try {
3153
+ if (typeof runtime.sendMessageToRoom === "function") {
3154
+ await runtime.sendMessageToRoom(session.roomId, {
3155
+ text: message
3156
+ });
3157
+ logger5.debug(`[FormNudge] Sent expiration warning for session in room ${session.roomId}`);
3158
+ }
3159
+ } catch (error) {
3160
+ logger5.error("[FormNudge] Failed to send expiration warning:", String(error));
3161
+ }
3162
+ }
3163
+
3164
+ // index.ts
3078
3165
  var formPlugin = {
3079
3166
  name: "form",
3080
3167
  description: "Agent-native conversational forms for data collection",
@@ -3143,7 +3230,7 @@ var formPlugin = {
3143
3230
  }
3144
3231
  ]
3145
3232
  };
3146
- var src_default = formPlugin;
3233
+ var typescript_default = formPlugin;
3147
3234
  export {
3148
3235
  validateField,
3149
3236
  shouldNudge,
@@ -3154,6 +3241,7 @@ export {
3154
3241
  registerTypeHandler,
3155
3242
  registerBuiltinTypes,
3156
3243
  quickIntentDetect,
3244
+ processEntityNudges,
3157
3245
  prettify,
3158
3246
  parseValue,
3159
3247
  matchesMimeType,
@@ -3176,12 +3264,13 @@ export {
3176
3264
  formatEffort,
3177
3265
  formRestoreAction,
3178
3266
  formPlugin,
3267
+ formNudgeWorker,
3179
3268
  formEvaluator,
3180
3269
  formContextProvider,
3181
3270
  extractSingleField,
3182
3271
  detectCorrection,
3183
3272
  deleteSession,
3184
- src_default as default,
3273
+ typescript_default as default,
3185
3274
  clearTypeHandlers,
3186
3275
  calculateTTL,
3187
3276
  applyFormDefaults,
@@ -3200,4 +3289,5 @@ export {
3200
3289
  BUILTIN_TYPES
3201
3290
  };
3202
3291
 
3203
- //# debugId=6EC545854C3E8D8A64756E2164756E21
3292
+ //# debugId=D9CCEBB86B648A7B64756E2164756E21
3293
+ //# sourceMappingURL=index.js.map