agent-conversation-journey-smoke-test.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. 'use strict';
  2. const assert = require('assert/strict');
  3. const {
  4. APPLIED_ACTION_STATUSES,
  5. CONFIRMING_EVIDENCE_TYPES,
  6. createJourneyDefinition,
  7. createJourneyState,
  8. applyJourneyFacts,
  9. isStageComplete,
  10. planAppliedAction,
  11. executeAppliedAction,
  12. confirmAppliedAction,
  13. serializeJourneyState,
  14. } = require('../mcp/src/core/agent-conversation-journey');
  15. function confirmation(type, id, at) {
  16. return { type, id, at, success: true };
  17. }
  18. function main() {
  19. const definition = createJourneyDefinition({
  20. id: 'generic-consultation',
  21. initialStageId: 'understand',
  22. stages: [
  23. { id: 'understand', requiredFacts: ['goal'] },
  24. { id: 'qualify', requiredFacts: ['constraints.budget', 'constraints.timeline'] },
  25. { id: 'recommend', requiredFacts: ['selection'] },
  26. { id: 'commit', requiredFacts: ['commitment'] },
  27. ],
  28. });
  29. assert.deepEqual(APPLIED_ACTION_STATUSES, ['planned', 'executed', 'confirmed']);
  30. assert.deepEqual(CONFIRMING_EVIDENCE_TYPES, ['send', 'tool', 'handoff']);
  31. const initial = createJourneyState(definition, { at: '2026-08-06T01:00:00.000Z' });
  32. const observed = applyJourneyFacts(definition, initial, {
  33. constraints: { timeline: 'soon', budget: 100 },
  34. goal: 'compare options',
  35. }, { at: '2026-08-06T01:01:00.000Z' });
  36. assert.equal(initial.facts.goal, undefined);
  37. assert.equal(observed.currentStageId, 'understand');
  38. assert.equal(isStageComplete(definition, observed, 'qualify'), true);
  39. const planned = planAppliedAction(definition, observed, {
  40. key: 'reply:message-1',
  41. kind: 'reply',
  42. transition: { mode: 'auto' },
  43. payload: { draftId: 'draft-1' },
  44. at: '2026-08-06T01:02:00.000Z',
  45. });
  46. assert.equal(planned.currentStageId, 'understand');
  47. assert.equal(planned.appliedActions[0].status, 'planned');
  48. const executed = executeAppliedAction(definition, planned, {
  49. key: 'reply:message-1',
  50. evidence: { type: 'send', id: 'attempt-1', success: false },
  51. at: '2026-08-06T01:03:00.000Z',
  52. });
  53. assert.equal(executed.currentStageId, 'understand');
  54. assert.equal(executed.appliedActions[0].status, 'executed');
  55. assert.throws(() => confirmAppliedAction(definition, executed, {
  56. key: 'reply:message-1',
  57. evidence: { type: 'draft', id: 'draft-1', success: true },
  58. }), /Unsupported action evidence type/);
  59. assert.throws(() => confirmAppliedAction(definition, executed, {
  60. key: 'reply:message-1',
  61. evidence: { type: 'send', id: 'message-1', success: false },
  62. }), /successful evidence/);
  63. const advanced = confirmAppliedAction(definition, executed, {
  64. key: 'reply:message-1',
  65. evidence: confirmation('send', 'message-1', '2026-08-06T01:04:00.000Z'),
  66. });
  67. assert.equal(advanced.currentStageId, 'recommend');
  68. assert.equal(advanced.appliedActions[0].status, 'confirmed');
  69. assert.deepEqual(advanced.appliedActions[0].transitionResult.skippedStageIds, ['qualify']);
  70. assert(advanced.stageHistory.some(entry => entry.kind === 'skip' && entry.stageId === 'qualify'));
  71. const backPlanned = planAppliedAction(definition, advanced, {
  72. key: 'handoff:requalify',
  73. kind: 'human_handoff',
  74. transition: { mode: 'back', targetStageId: 'qualify' },
  75. });
  76. const backed = confirmAppliedAction(definition, backPlanned, {
  77. key: 'handoff:requalify',
  78. evidence: confirmation('handoff', 'handoff-1', '2026-08-06T01:05:00.000Z'),
  79. });
  80. assert.equal(backed.currentStageId, 'qualify');
  81. assert.equal(backed.stageHistory.at(-1).kind, 'back');
  82. const reenterPlanned = planAppliedAction(definition, backed, {
  83. key: 'tool:refresh-qualification',
  84. kind: 'refresh',
  85. transition: { mode: 'reenter', targetStageId: 'qualify' },
  86. });
  87. const reentered = confirmAppliedAction(definition, reenterPlanned, {
  88. key: 'tool:refresh-qualification',
  89. evidence: confirmation('tool', 'tool-run-1', '2026-08-06T01:06:00.000Z'),
  90. });
  91. assert.equal(reentered.currentStageId, 'qualify');
  92. assert.equal(reentered.stageHistory.at(-1).kind, 'reenter');
  93. const skipPlanned = planAppliedAction(definition, reentered, {
  94. key: 'reply:direct-commit',
  95. kind: 'reply',
  96. transition: { mode: 'to', targetStageId: 'commit' },
  97. });
  98. const skipped = confirmAppliedAction(definition, skipPlanned, {
  99. key: 'reply:direct-commit',
  100. evidence: confirmation('send', 'message-2', '2026-08-06T01:07:00.000Z'),
  101. });
  102. assert.equal(skipped.currentStageId, 'commit');
  103. assert.deepEqual(skipped.appliedActions.at(-1).transitionResult.skippedStageIds, ['recommend']);
  104. const deduped = planAppliedAction(definition, skipped, {
  105. key: 'reply:direct-commit',
  106. kind: 'reply',
  107. transition: { mode: 'to', targetStageId: 'commit' },
  108. });
  109. assert.equal(deduped.appliedActions.length, skipped.appliedActions.length);
  110. assert.equal(deduped.sequence, skipped.sequence);
  111. assert.throws(() => planAppliedAction(definition, skipped, {
  112. key: 'reply:direct-commit',
  113. kind: 'different-action',
  114. transition: { mode: 'none' },
  115. }), /key conflict/);
  116. const serialized = serializeJourneyState(skipped);
  117. assert.equal(serialized, serializeJourneyState(JSON.parse(serialized)));
  118. const reordered = {
  119. ...skipped,
  120. facts: {
  121. goal: skipped.facts.goal,
  122. constraints: {
  123. budget: skipped.facts.constraints.budget,
  124. timeline: skipped.facts.constraints.timeline,
  125. },
  126. },
  127. };
  128. assert.equal(serialized, serializeJourneyState(reordered));
  129. assert(skipped.appliedActions.every(action => APPLIED_ACTION_STATUSES.includes(action.status)));
  130. process.stdout.write(`${JSON.stringify({
  131. status: 'ok',
  132. checks: 25,
  133. finalStage: skipped.currentStageId,
  134. actions: skipped.appliedActions.length,
  135. historyEntries: skipped.stageHistory.length,
  136. }, null, 2)}\n`);
  137. }
  138. try { main(); }
  139. catch (error) {
  140. process.stderr.write(`${error.stack || error.message}\n`);
  141. process.exit(1);
  142. }