dominds 1.28.0 → 1.29.0

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.
Files changed (51) hide show
  1. package/README.md +1 -0
  2. package/README.zh.md +1 -0
  3. package/dist/dialog-global-registry.d.ts +2 -0
  4. package/dist/dialog-global-registry.js +41 -0
  5. package/dist/dialog.d.ts +1 -0
  6. package/dist/dialog.js +5 -0
  7. package/dist/docs/codex-provider-auth-policy.md +59 -0
  8. package/dist/docs/codex-provider-auth-policy.zh.md +59 -0
  9. package/dist/docs/llm-provider-isolation.md +1 -1
  10. package/dist/docs/llm-provider-isolation.zh.md +1 -1
  11. package/dist/llm/api-quirks.d.ts +1 -0
  12. package/dist/llm/api-quirks.js +2 -1
  13. package/dist/llm/client.d.ts +2 -1
  14. package/dist/llm/defaults.yaml +163 -7
  15. package/dist/llm/gen/codex.d.ts +3 -2
  16. package/dist/llm/gen/codex.js +82 -10
  17. package/dist/llm/gen/google.d.ts +20 -0
  18. package/dist/llm/gen/google.js +735 -0
  19. package/dist/llm/gen/mock.js +8 -0
  20. package/dist/llm/gen/openai-compatible.js +5 -0
  21. package/dist/llm/gen/openai.js +9 -1
  22. package/dist/llm/gen/registry.js +4 -2
  23. package/dist/llm/gen/tool-output-limit.js +1 -0
  24. package/dist/llm/gen/tool-result-image-ingest.d.ts +1 -0
  25. package/dist/llm/gen/tool-result-image-ingest.js +2 -1
  26. package/dist/llm/kernel-driver/drive.js +21 -3
  27. package/dist/main-dialog-goal-reminder.d.ts +20 -0
  28. package/dist/main-dialog-goal-reminder.js +344 -0
  29. package/dist/minds/system-prompt-parts.js +14 -14
  30. package/dist/runtime/driver-messages.js +16 -16
  31. package/dist/server/setup-routes.js +3 -1
  32. package/dist/team.d.ts +12 -3
  33. package/dist/team.js +36 -4
  34. package/dist/tools/builtins.js +2 -0
  35. package/dist/tools/ctrl.d.ts +1 -0
  36. package/dist/tools/ctrl.js +65 -1
  37. package/dist/tools/prompts/control/en/index.md +4 -2
  38. package/dist/tools/prompts/control/en/principles.md +5 -4
  39. package/dist/tools/prompts/control/en/scenarios.md +9 -4
  40. package/dist/tools/prompts/control/en/tools.md +53 -9
  41. package/dist/tools/prompts/control/zh/index.md +4 -2
  42. package/dist/tools/prompts/control/zh/principles.md +5 -4
  43. package/dist/tools/prompts/control/zh/scenarios.md +9 -4
  44. package/dist/tools/prompts/control/zh/tools.md +53 -9
  45. package/dist/tools/team_mgmt-manual.js +10 -8
  46. package/dist/tools/team_mgmt.js +3 -0
  47. package/dist/tools/txt.js +115 -70
  48. package/package.json +5 -4
  49. package/webapp/dist/assets/{main-YWP5PWOM.js → main-YWYVZGBW.js} +119 -60
  50. package/webapp/dist/assets/{main-YWP5PWOM.js.map → main-YWYVZGBW.js.map} +3 -3
  51. package/webapp/dist/index.html +1 -1
@@ -428,6 +428,9 @@ responses:
428
428
  else if (matched && matched.usage) {
429
429
  const promptTokens = matched.usage.promptTokens;
430
430
  const completionTokens = typeof matched.usage.completionTokens === 'number' ? matched.usage.completionTokens : 0;
431
+ const reasoningTokens = typeof matched.usage.reasoningTokens === 'number'
432
+ ? matched.usage.reasoningTokens
433
+ : undefined;
431
434
  const totalTokens = typeof matched.usage.totalTokens === 'number'
432
435
  ? matched.usage.totalTokens
433
436
  : promptTokens + completionTokens;
@@ -435,6 +438,7 @@ responses:
435
438
  kind: 'available',
436
439
  promptTokens,
437
440
  completionTokens,
441
+ ...(reasoningTokens === undefined ? {} : { reasoningTokens }),
438
442
  totalTokens,
439
443
  };
440
444
  }
@@ -547,6 +551,9 @@ responses:
547
551
  else if (matched && matched.usage) {
548
552
  const promptTokens = matched.usage.promptTokens;
549
553
  const completionTokens = typeof matched.usage.completionTokens === 'number' ? matched.usage.completionTokens : 0;
554
+ const reasoningTokens = typeof matched.usage.reasoningTokens === 'number'
555
+ ? matched.usage.reasoningTokens
556
+ : undefined;
550
557
  const totalTokens = typeof matched.usage.totalTokens === 'number'
551
558
  ? matched.usage.totalTokens
552
559
  : promptTokens + completionTokens;
@@ -554,6 +561,7 @@ responses:
554
561
  kind: 'available',
555
562
  promptTokens,
556
563
  completionTokens,
564
+ ...(reasoningTokens === undefined ? {} : { reasoningTokens }),
557
565
  totalTokens,
558
566
  };
559
567
  }
@@ -777,12 +777,17 @@ function tryExtractChatUsage(usage) {
777
777
  const prompt = usage.prompt_tokens;
778
778
  const completion = usage.completion_tokens;
779
779
  const total = usage.total_tokens;
780
+ const completionDetails = usage.completion_tokens_details;
780
781
  if (typeof prompt !== 'number' || typeof completion !== 'number')
781
782
  return { kind: 'unavailable' };
783
+ const reasoningTokens = isRecord(completionDetails)
784
+ ? completionDetails.reasoning_tokens
785
+ : undefined;
782
786
  return {
783
787
  kind: 'available',
784
788
  promptTokens: prompt,
785
789
  completionTokens: completion,
790
+ ...(typeof reasoningTokens === 'number' ? { reasoningTokens } : {}),
786
791
  totalTokens: typeof total === 'number' ? total : prompt + completion,
787
792
  };
788
793
  }
@@ -600,13 +600,18 @@ function parseOpenAiUsage(usage) {
600
600
  const inputTokens = usage.input_tokens;
601
601
  const outputTokens = usage.output_tokens;
602
602
  const totalTokens = usage.total_tokens;
603
+ const outputTokensDetails = usage.output_tokens_details;
603
604
  if (typeof inputTokens !== 'number' || typeof outputTokens !== 'number') {
604
605
  return { kind: 'unavailable' };
605
606
  }
607
+ const reasoningTokens = isRecord(outputTokensDetails)
608
+ ? outputTokensDetails.reasoning_tokens
609
+ : undefined;
606
610
  return {
607
611
  kind: 'available',
608
612
  promptTokens: inputTokens,
609
613
  completionTokens: outputTokens,
614
+ ...(typeof reasoningTokens === 'number' ? { reasoningTokens } : {}),
610
615
  totalTokens: typeof totalTokens === 'number' ? totalTokens : inputTokens + outputTokens,
611
616
  };
612
617
  }
@@ -654,12 +659,15 @@ function buildOpenAiReasoning(openAiParams) {
654
659
  return undefined;
655
660
  }
656
661
  const summary = openAiParams.reasoning_summary === 'none' ? null : openAiParams.reasoning_summary;
657
- return {
662
+ const reasoning = {
658
663
  ...(openAiParams.reasoning_effort !== undefined
659
664
  ? { effort: openAiParams.reasoning_effort }
660
665
  : {}),
661
666
  ...(summary !== undefined ? { summary } : {}),
662
667
  };
668
+ // OpenAI SDK 6.42.0 predates the GPT-5.6 `max` effort literal exposed by the current
669
+ // API model catalog. Keep the widening isolated at this external SDK boundary.
670
+ return reasoning;
663
671
  }
664
672
  function buildOpenAiNativeTools(openAiParams) {
665
673
  const enabled = openAiParams.web_search_tool === true ||
@@ -6,6 +6,7 @@ exports.unregisterLlmGenerator = unregisterLlmGenerator;
6
6
  exports.getLlmGenerator = getLlmGenerator;
7
7
  const anthropic_1 = require("./anthropic");
8
8
  const codex_1 = require("./codex");
9
+ const google_1 = require("./google");
9
10
  const mock_1 = require("./mock");
10
11
  const openai_1 = require("./openai");
11
12
  const openai_compatible_1 = require("./openai-compatible");
@@ -20,10 +21,11 @@ function getLlmGenerator(apiType) {
20
21
  return exports.generatorsRegistry.get(apiType);
21
22
  }
22
23
  (function initializeBuiltins() {
24
+ registerLlmGenerator(new mock_1.MockGen());
23
25
  registerLlmGenerator(new anthropic_1.AnthropicGen());
24
26
  registerLlmGenerator(new anthropic_1.AnthropicGen('anthropic-compatible'));
25
- registerLlmGenerator(new codex_1.CodexGen());
26
- registerLlmGenerator(new mock_1.MockGen());
27
27
  registerLlmGenerator(new openai_1.OpenAiGen());
28
28
  registerLlmGenerator(new openai_compatible_1.OpenAiCompatibleGen());
29
+ registerLlmGenerator(new codex_1.CodexGen());
30
+ registerLlmGenerator(new google_1.GoogleGen());
29
31
  })();
@@ -8,6 +8,7 @@ const DEFAULT_PROVIDER_TOOL_OUTPUT_CHAR_LIMITS = {
8
8
  'openai-compatible': 4 * 1024 * 1024,
9
9
  anthropic: 4 * 1024 * 1024,
10
10
  'anthropic-compatible': 4 * 1024 * 1024,
11
+ google: 4 * 1024 * 1024,
11
12
  mock: 8 * 1024 * 1024,
12
13
  };
13
14
  function buildProviderToolOutputSuffix(originalChars, limitChars) {
@@ -14,6 +14,7 @@ export declare const OPENAI_TOOL_RESULT_IMAGE_BUDGET_BYTES: number;
14
14
  export declare const OPENAI_COMPATIBLE_TOOL_RESULT_IMAGE_BUDGET_BYTES: number;
15
15
  export declare const CODEX_TOOL_RESULT_IMAGE_BUDGET_BYTES: number;
16
16
  export declare const ANTHROPIC_TOOL_RESULT_IMAGE_BUDGET_BYTES: number;
17
+ export declare const GEMINI_TOOL_RESULT_IMAGE_BUDGET_BYTES: number;
17
18
  export declare function resolveModelImageInputSupport(modelInfo: ModelInfo | undefined, defaultValue: boolean): boolean;
18
19
  export declare function buildImageBudgetLimitDetail(args: {
19
20
  byteLength: number;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ANTHROPIC_TOOL_RESULT_IMAGE_BUDGET_BYTES = exports.CODEX_TOOL_RESULT_IMAGE_BUDGET_BYTES = exports.OPENAI_COMPATIBLE_TOOL_RESULT_IMAGE_BUDGET_BYTES = exports.OPENAI_TOOL_RESULT_IMAGE_BUDGET_BYTES = void 0;
3
+ exports.GEMINI_TOOL_RESULT_IMAGE_BUDGET_BYTES = exports.ANTHROPIC_TOOL_RESULT_IMAGE_BUDGET_BYTES = exports.CODEX_TOOL_RESULT_IMAGE_BUDGET_BYTES = exports.OPENAI_COMPATIBLE_TOOL_RESULT_IMAGE_BUDGET_BYTES = exports.OPENAI_TOOL_RESULT_IMAGE_BUDGET_BYTES = void 0;
4
4
  exports.resolveModelImageInputSupport = resolveModelImageInputSupport;
5
5
  exports.buildImageBudgetLimitDetail = buildImageBudgetLimitDetail;
6
6
  exports.buildImageBudgetKeyForContentItem = buildImageBudgetKeyForContentItem;
@@ -24,6 +24,7 @@ exports.OPENAI_TOOL_RESULT_IMAGE_BUDGET_BYTES = 50 * 1024 * 1024;
24
24
  exports.OPENAI_COMPATIBLE_TOOL_RESULT_IMAGE_BUDGET_BYTES = 50 * 1024 * 1024;
25
25
  exports.CODEX_TOOL_RESULT_IMAGE_BUDGET_BYTES = 50 * 1024 * 1024;
26
26
  exports.ANTHROPIC_TOOL_RESULT_IMAGE_BUDGET_BYTES = 32 * 1024 * 1024;
27
+ exports.GEMINI_TOOL_RESULT_IMAGE_BUDGET_BYTES = 20 * 1024 * 1024;
27
28
  function resolveModelImageInputSupport(modelInfo, defaultValue) {
28
29
  const value = modelInfo?.['supports_image_input'];
29
30
  if (value === undefined)
@@ -793,7 +793,7 @@ function formatContextHealthLargeToolReturnUnavailable(args) {
793
793
  '',
794
794
  '不要再尝试获取各种大段的输出,都不会显示给你。现在先做两件事:',
795
795
  '1. 把下一程对话需要知道的此程细节信息写入差遣牒合适章节。',
796
- '2. 对于不适合差遣牒章节覆盖、但下一程恢复当前对话需要的信息,用当前对话范围(scope=dialog)提醒项写明本路对话任务目标并带过桥。',
796
+ '2. 先看固定“本路主线目标”提醒;它要求先问人类就立即问,并用 set_dialog_goal 记录答案。对于不适合差遣牒章节覆盖、但下一程恢复当前对话需要的信息,用当前对话范围(scope=dialog)提醒项带过桥。',
797
797
  '',
798
798
  '然后调用 clear_mind({}) 开启新一程。',
799
799
  '',
@@ -818,7 +818,7 @@ function formatContextHealthLargeToolReturnUnavailable(args) {
818
818
  '',
819
819
  'Do not try again to fetch any kind of large output; it still will not be shown. Do two things now:',
820
820
  '1. Write the details from this course that the next course needs into the appropriate Taskdoc sections.',
821
- '2. For information that does not fit a Taskdoc section but is needed to resume this dialog in the next course, use current-dialog scoped (scope=dialog) reminders to state this dialog task goal and carry it over.',
821
+ '2. First read the fixed Main Dialog goal reminder. If it says to ask the human, ask and record the answer with set_dialog_goal. For information that does not fit a Taskdoc section but is needed to resume this dialog in the next course, use current-dialog scoped (scope=dialog) reminders to carry it over.',
822
822
  '',
823
823
  'Then call clear_mind({}) to start a new course.',
824
824
  '',
@@ -922,13 +922,28 @@ function mergeTellaskVirtualTools(baseTools, options) {
922
922
  return merged;
923
923
  }
924
924
  function computeContextHealthSnapshot(args) {
925
+ const usageFields = args.usage.kind === 'available'
926
+ ? {
927
+ promptTokens: args.usage.promptTokens,
928
+ completionTokens: args.usage.completionTokens,
929
+ ...(typeof args.usage.reasoningTokens === 'number'
930
+ ? { reasoningTokens: args.usage.reasoningTokens }
931
+ : {}),
932
+ totalTokens: args.usage.totalTokens,
933
+ }
934
+ : {};
925
935
  const modelInfo = args.providerCfg.models[args.model];
926
936
  const modelContextWindowText = modelInfo && typeof modelInfo.context_window === 'string'
927
937
  ? modelInfo.context_window
928
938
  : undefined;
929
939
  const modelContextLimitTokens = resolveModelContextLimitTokens(modelInfo);
930
940
  if (modelContextLimitTokens === null) {
931
- return { kind: 'unavailable', reason: 'model_limit_unavailable', modelContextWindowText };
941
+ return {
942
+ kind: 'unavailable',
943
+ reason: 'model_limit_unavailable',
944
+ ...usageFields,
945
+ modelContextWindowText,
946
+ };
932
947
  }
933
948
  const { effectiveOptimalMaxTokens, optimalMaxTokensConfigured, effectiveCriticalMaxTokens, criticalMaxTokensConfigured, } = resolveEffectiveTokenThresholds({
934
949
  modelInfo,
@@ -957,6 +972,9 @@ function computeContextHealthSnapshot(args) {
957
972
  kind: 'available',
958
973
  promptTokens: args.usage.promptTokens,
959
974
  completionTokens: args.usage.completionTokens,
975
+ ...(typeof args.usage.reasoningTokens === 'number'
976
+ ? { reasoningTokens: args.usage.reasoningTokens }
977
+ : {}),
960
978
  totalTokens: args.usage.totalTokens,
961
979
  modelContextWindowText,
962
980
  modelContextLimitTokens,
@@ -0,0 +1,20 @@
1
+ import type { Dialog } from './dialog';
2
+ import { type Reminder } from './tool';
3
+ export declare const MAIN_DIALOG_GOAL_REMINDER_ID = "mainDialogGoal";
4
+ export type SetMainDialogGoalRequest = Readonly<{
5
+ mode: 'goal';
6
+ goal: string;
7
+ }> | Readonly<{
8
+ mode: 'follow_taskdoc';
9
+ }>;
10
+ export type SetMainDialogGoalResult = Readonly<{
11
+ kind: 'updated';
12
+ reminder: Reminder;
13
+ }> | Readonly<{
14
+ kind: 'rejected_parallel_dialogs';
15
+ reminder: Reminder;
16
+ }>;
17
+ export declare function ensureMainDialogGoalReminder(dlg: Dialog): Promise<boolean>;
18
+ export declare function reconcileMainDialogGoalReminderForKnownParallelState(dlg: Dialog, hasParallelDialogs: boolean): boolean;
19
+ export declare function refreshFollowTaskdocMainDialogGoalReminderForKnownParallelState(dlg: Dialog): boolean;
20
+ export declare function setMainDialogGoalReminder(dlg: Dialog, request: SetMainDialogGoalRequest): Promise<SetMainDialogGoalResult>;
@@ -0,0 +1,344 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAIN_DIALOG_GOAL_REMINDER_ID = void 0;
4
+ exports.ensureMainDialogGoalReminder = ensureMainDialogGoalReminder;
5
+ exports.reconcileMainDialogGoalReminderForKnownParallelState = reconcileMainDialogGoalReminderForKnownParallelState;
6
+ exports.refreshFollowTaskdocMainDialogGoalReminderForKnownParallelState = refreshFollowTaskdocMainDialogGoalReminderForKnownParallelState;
7
+ exports.setMainDialogGoalReminder = setMainDialogGoalReminder;
8
+ const time_1 = require("@longrun-ai/kernel/utils/time");
9
+ const work_language_1 = require("./runtime/work-language");
10
+ const tool_1 = require("./tool");
11
+ exports.MAIN_DIALOG_GOAL_REMINDER_ID = 'mainDialogGoal';
12
+ function isRecord(value) {
13
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
14
+ }
15
+ function isMainDialog(dlg) {
16
+ return dlg.id.selfId === dlg.id.rootId;
17
+ }
18
+ function parseMainDialogGoalMeta(meta) {
19
+ if (!isRecord(meta)) {
20
+ return undefined;
21
+ }
22
+ if (meta['kind'] !== 'main_dialog_goal') {
23
+ return undefined;
24
+ }
25
+ const mode = meta['mode'];
26
+ if (mode !== 'requires_human_confirmation' &&
27
+ mode !== 'specific_goal' &&
28
+ mode !== 'follow_taskdoc') {
29
+ throw new Error(`Invalid main-dialog goal reminder mode: ${String(mode)}`);
30
+ }
31
+ const goal = meta['goal'];
32
+ const specificGoal = mode === 'specific_goal' && typeof goal === 'string' ? goal : undefined;
33
+ if (mode === 'specific_goal') {
34
+ if (specificGoal === undefined || specificGoal.trim() === '') {
35
+ throw new Error('Invalid main-dialog goal reminder meta: specific_goal requires goal');
36
+ }
37
+ }
38
+ else if (goal !== undefined) {
39
+ throw new Error('Invalid main-dialog goal reminder meta: goal is only valid for specific_goal');
40
+ }
41
+ const updatedAt = meta['updatedAt'];
42
+ if (typeof updatedAt !== 'string' || updatedAt.trim() === '') {
43
+ throw new Error('Invalid main-dialog goal reminder meta: updatedAt required');
44
+ }
45
+ const update = meta['update'];
46
+ if (!isRecord(update) || typeof update['altInstruction'] !== 'string') {
47
+ throw new Error('Invalid main-dialog goal reminder meta: update.altInstruction required');
48
+ }
49
+ const deleteValue = meta['delete'];
50
+ if (!isRecord(deleteValue) || typeof deleteValue['altInstruction'] !== 'string') {
51
+ throw new Error('Invalid main-dialog goal reminder meta: delete.altInstruction required');
52
+ }
53
+ const base = {
54
+ kind: 'main_dialog_goal',
55
+ updatedAt,
56
+ update: { altInstruction: update['altInstruction'] },
57
+ delete: { altInstruction: deleteValue['altInstruction'] },
58
+ };
59
+ switch (mode) {
60
+ case 'requires_human_confirmation':
61
+ return { ...base, mode };
62
+ case 'follow_taskdoc':
63
+ return { ...base, mode };
64
+ case 'specific_goal': {
65
+ if (specificGoal === undefined) {
66
+ throw new Error('Invalid main-dialog goal reminder meta: specific_goal requires goal');
67
+ }
68
+ return { ...base, mode, goal: specificGoal };
69
+ }
70
+ }
71
+ }
72
+ function mainDialogGoalMaintenanceInstruction() {
73
+ return ('set_dialog_goal({ "mode": "goal", "goal": "..." }) ' +
74
+ 'or set_dialog_goal({ "mode": "follow_taskdoc" })');
75
+ }
76
+ function buildMainDialogGoalMeta(args) {
77
+ if (args.mode === 'specific_goal') {
78
+ if (args.goal.trim() === '') {
79
+ throw new Error('Main-dialog goal reminder specific_goal meta requires a non-empty goal');
80
+ }
81
+ }
82
+ else if ('goal' in args && args.goal !== undefined) {
83
+ throw new Error('Main-dialog goal reminder goal is only valid for specific_goal meta');
84
+ }
85
+ const instruction = mainDialogGoalMaintenanceInstruction();
86
+ const base = {
87
+ kind: 'main_dialog_goal',
88
+ updatedAt: args.updatedAt ?? (0, time_1.formatUnifiedTimestamp)(new Date()),
89
+ update: { altInstruction: instruction },
90
+ delete: { altInstruction: instruction },
91
+ };
92
+ switch (args.mode) {
93
+ case 'requires_human_confirmation':
94
+ return { ...base, mode: args.mode };
95
+ case 'follow_taskdoc':
96
+ return { ...base, mode: args.mode };
97
+ case 'specific_goal':
98
+ return { ...base, mode: args.mode, goal: args.goal };
99
+ }
100
+ }
101
+ function formatMainDialogGoalContent(language, meta, hasParallelDialogs) {
102
+ if (language === 'zh') {
103
+ switch (meta.mode) {
104
+ case 'specific_goal':
105
+ return `本路主线目标:\n${meta.goal}`;
106
+ case 'follow_taskdoc':
107
+ return hasParallelDialogs
108
+ ? [
109
+ '本路主线目标:依差遣牒推进。',
110
+ 'Dominds 已确认:这个智能体现在还有其它并行对话。不能只按差遣牒继续;请立即问人类:这一路主线接下来具体推进什么?得到答案后用 set_dialog_goal 记录。',
111
+ ].join('\n')
112
+ : [
113
+ '本路主线目标:依差遣牒推进。',
114
+ 'Dominds 已确认:当前这个智能体只有这一条对话,可以按差遣牒继续。若以后出现同智能体并行对话,Dominds 会在这条提醒上补充“先问人类”。',
115
+ ].join('\n');
116
+ case 'requires_human_confirmation':
117
+ return hasParallelDialogs
118
+ ? [
119
+ '本路主线目标:未设置。',
120
+ 'Dominds 已确认:这个智能体现在有其它并行对话。请立即问人类:这一路主线接下来具体推进什么?得到答案后用 set_dialog_goal 记录。',
121
+ ].join('\n')
122
+ : [
123
+ '本路主线目标:未设置。',
124
+ '请立即问人类:这一路主线接下来具体推进什么?得到答案后用 set_dialog_goal 记录。',
125
+ ].join('\n');
126
+ }
127
+ }
128
+ switch (meta.mode) {
129
+ case 'specific_goal':
130
+ return `Goal for this Main Dialog:\n${meta.goal}`;
131
+ case 'follow_taskdoc':
132
+ return hasParallelDialogs
133
+ ? [
134
+ 'Goal for this Main Dialog: proceed from the Taskdoc.',
135
+ 'Dominds has confirmed that this agent now has another parallel dialog. Do not continue from the Taskdoc alone. Ask the human immediately: what should this Main Dialog work on next? Then record the answer with set_dialog_goal.',
136
+ ].join('\n')
137
+ : [
138
+ 'Goal for this Main Dialog: proceed from the Taskdoc.',
139
+ 'Dominds has confirmed that this agent has only one dialog right now, so following the Taskdoc is enough. If another parallel dialog appears for this agent, Dominds will add an "ask the human first" note to this reminder.',
140
+ ].join('\n');
141
+ case 'requires_human_confirmation':
142
+ return hasParallelDialogs
143
+ ? [
144
+ 'Goal for this Main Dialog: not set.',
145
+ 'Dominds has confirmed that this agent now has another parallel dialog. Ask the human immediately: what should this Main Dialog work on next? Then record the answer with set_dialog_goal.',
146
+ ].join('\n')
147
+ : [
148
+ 'Goal for this Main Dialog: not set.',
149
+ 'Ask the human immediately: what should this Main Dialog work on next? Then record the answer with set_dialog_goal.',
150
+ ].join('\n');
151
+ }
152
+ }
153
+ function buildMainDialogGoalReminder(args) {
154
+ return (0, tool_1.materializeReminder)({
155
+ id: exports.MAIN_DIALOG_GOAL_REMINDER_ID,
156
+ content: formatMainDialogGoalContent(args.language, args.meta, args.hasParallelDialogs),
157
+ meta: args.meta,
158
+ echoback: true,
159
+ scope: 'dialog',
160
+ createdAt: args.existing?.createdAt ?? args.meta.updatedAt,
161
+ priority: 'high',
162
+ renderMode: 'markdown',
163
+ });
164
+ }
165
+ function mainDialogGoalMetaEquals(value, expected) {
166
+ if (!isRecord(value)) {
167
+ return false;
168
+ }
169
+ const actual = parseMainDialogGoalMeta(value);
170
+ const expectedKeys = expected.goal === undefined
171
+ ? ['delete', 'kind', 'mode', 'update', 'updatedAt']
172
+ : ['delete', 'goal', 'kind', 'mode', 'update', 'updatedAt'];
173
+ return (actual !== undefined &&
174
+ Object.keys(value).sort().join('\0') === expectedKeys.sort().join('\0') &&
175
+ actual.mode === expected.mode &&
176
+ actual.goal === expected.goal &&
177
+ actual.updatedAt === expected.updatedAt &&
178
+ actual.update.altInstruction === expected.update.altInstruction &&
179
+ actual.delete.altInstruction === expected.delete.altInstruction);
180
+ }
181
+ function reminderMatchesMainDialogGoalState(existing, next, meta) {
182
+ return (existing.content === next.content &&
183
+ mainDialogGoalMetaEquals(existing.meta, meta) &&
184
+ existing.echoback === true &&
185
+ existing.scope === next.scope &&
186
+ existing.renderMode === next.renderMode &&
187
+ existing.priority === next.priority);
188
+ }
189
+ function findMainDialogGoalReminderIndex(reminders) {
190
+ let foundIndex;
191
+ for (let index = 0; index < reminders.length; index += 1) {
192
+ const reminder = reminders[index];
193
+ if (reminder === undefined) {
194
+ continue;
195
+ }
196
+ const meta = parseMainDialogGoalMeta(reminder.meta);
197
+ const hasFixedId = reminder.id === exports.MAIN_DIALOG_GOAL_REMINDER_ID;
198
+ if (hasFixedId && meta === undefined) {
199
+ throw new Error(`Reminder id ${exports.MAIN_DIALOG_GOAL_REMINDER_ID} is reserved for the main-dialog goal reminder`);
200
+ }
201
+ if (meta !== undefined && !hasFixedId) {
202
+ throw new Error(`Main-dialog goal reminder must use id ${exports.MAIN_DIALOG_GOAL_REMINDER_ID}; got ${reminder.id}`);
203
+ }
204
+ if (!hasFixedId) {
205
+ continue;
206
+ }
207
+ if (reminder.scope !== 'dialog') {
208
+ throw new Error('Main-dialog goal reminder must be dialog-scoped');
209
+ }
210
+ if (foundIndex !== undefined) {
211
+ throw new Error(`Duplicate main-dialog goal reminder detected in dialog-local reminders`);
212
+ }
213
+ foundIndex = index;
214
+ }
215
+ return foundIndex;
216
+ }
217
+ async function countRunningDialogsForAgentIncludingCurrent(dlg) {
218
+ if (dlg.status !== 'running') {
219
+ return 1;
220
+ }
221
+ const { globalDialogRegistry } = await import('./dialog-global-registry.js');
222
+ const runningDialogs = globalDialogRegistry.getRunningDialogsByAgent(dlg.agentId);
223
+ const includesCurrent = runningDialogs.some((candidate) => candidate.id.equals(dlg.id));
224
+ return runningDialogs.length + (includesCurrent ? 0 : 1);
225
+ }
226
+ async function hasParallelRunningDialogsForAgent(dlg) {
227
+ return (await countRunningDialogsForAgentIncludingCurrent(dlg)) > 1;
228
+ }
229
+ function normalizeExistingMetaForReconcile(args) {
230
+ const existingMeta = parseMainDialogGoalMeta(args.existing?.meta);
231
+ if (existingMeta === undefined) {
232
+ return buildMainDialogGoalMeta({
233
+ mode: 'requires_human_confirmation',
234
+ });
235
+ }
236
+ return existingMeta;
237
+ }
238
+ function upsertMainDialogGoalReminder(args) {
239
+ const index = findMainDialogGoalReminderIndex(args.dlg.reminders);
240
+ if (index === undefined) {
241
+ const next = buildMainDialogGoalReminder({
242
+ meta: args.meta,
243
+ language: (0, work_language_1.getWorkLanguage)(),
244
+ hasParallelDialogs: args.hasParallelDialogs,
245
+ });
246
+ args.dlg.reminders.unshift(next);
247
+ args.dlg.touchReminders();
248
+ return { changed: true, reminder: next };
249
+ }
250
+ const existing = args.dlg.reminders[index];
251
+ if (existing === undefined) {
252
+ throw new Error(`Main-dialog goal reminder index ${index} disappeared before update`);
253
+ }
254
+ const next = buildMainDialogGoalReminder({
255
+ existing,
256
+ meta: args.meta,
257
+ language: (0, work_language_1.getWorkLanguage)(),
258
+ hasParallelDialogs: args.hasParallelDialogs,
259
+ });
260
+ if (reminderMatchesMainDialogGoalState(existing, next, args.meta)) {
261
+ return { changed: false, reminder: existing };
262
+ }
263
+ args.dlg.reminders[index] = next;
264
+ args.dlg.touchReminders();
265
+ return { changed: true, reminder: next };
266
+ }
267
+ async function ensureMainDialogGoalReminder(dlg) {
268
+ if (!isMainDialog(dlg)) {
269
+ return false;
270
+ }
271
+ const hasParallelDialogs = await hasParallelRunningDialogsForAgent(dlg);
272
+ return reconcileMainDialogGoalReminderForKnownParallelState(dlg, hasParallelDialogs);
273
+ }
274
+ function reconcileMainDialogGoalReminderForKnownParallelState(dlg, hasParallelDialogs) {
275
+ if (!isMainDialog(dlg)) {
276
+ return false;
277
+ }
278
+ const index = findMainDialogGoalReminderIndex(dlg.reminders);
279
+ const existing = index === undefined ? undefined : dlg.reminders[index];
280
+ const meta = normalizeExistingMetaForReconcile({
281
+ existing,
282
+ });
283
+ return upsertMainDialogGoalReminder({ dlg, meta, hasParallelDialogs }).changed;
284
+ }
285
+ function refreshFollowTaskdocMainDialogGoalReminderForKnownParallelState(dlg) {
286
+ if (!isMainDialog(dlg)) {
287
+ return false;
288
+ }
289
+ const index = findMainDialogGoalReminderIndex(dlg.reminders);
290
+ if (index === undefined) {
291
+ return false;
292
+ }
293
+ const existing = dlg.reminders[index];
294
+ if (existing === undefined) {
295
+ throw new Error(`Main-dialog goal reminder index ${index} disappeared before parallel marking`);
296
+ }
297
+ const existingMeta = parseMainDialogGoalMeta(existing.meta);
298
+ if (existingMeta?.mode !== 'follow_taskdoc') {
299
+ return false;
300
+ }
301
+ return upsertMainDialogGoalReminder({
302
+ dlg,
303
+ meta: existingMeta,
304
+ hasParallelDialogs: true,
305
+ }).changed;
306
+ }
307
+ async function setMainDialogGoalReminder(dlg, request) {
308
+ if (!isMainDialog(dlg)) {
309
+ throw new Error('setMainDialogGoalReminder is only valid for Main Dialogs');
310
+ }
311
+ const hasParallelDialogs = await hasParallelRunningDialogsForAgent(dlg);
312
+ if (request.mode === 'follow_taskdoc' && hasParallelDialogs) {
313
+ const rejected = upsertMainDialogGoalReminder({
314
+ dlg,
315
+ meta: buildMainDialogGoalMeta({
316
+ mode: 'follow_taskdoc',
317
+ }),
318
+ hasParallelDialogs,
319
+ });
320
+ return { kind: 'rejected_parallel_dialogs', reminder: rejected.reminder };
321
+ }
322
+ if (request.mode === 'follow_taskdoc') {
323
+ return {
324
+ kind: 'updated',
325
+ reminder: upsertMainDialogGoalReminder({
326
+ dlg,
327
+ meta: buildMainDialogGoalMeta({ mode: 'follow_taskdoc' }),
328
+ hasParallelDialogs,
329
+ }).reminder,
330
+ };
331
+ }
332
+ const goal = request.goal.trim();
333
+ if (goal === '') {
334
+ throw new Error('setMainDialogGoalReminder requires a non-empty goal');
335
+ }
336
+ return {
337
+ kind: 'updated',
338
+ reminder: upsertMainDialogGoalReminder({
339
+ dlg,
340
+ meta: buildMainDialogGoalMeta({ mode: 'specific_goal', goal }),
341
+ hasParallelDialogs,
342
+ }).reminder,
343
+ };
344
+ }