dominds 1.27.1 → 1.27.3

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 (44) hide show
  1. package/dist/apps/runtime.js +3 -1
  2. package/dist/dialog-global-registry.d.ts +11 -1
  3. package/dist/dialog-global-registry.js +45 -0
  4. package/dist/dialog.d.ts +13 -3
  5. package/dist/dialog.js +108 -17
  6. package/dist/docs/daemon-cmd-runner.md +5 -0
  7. package/dist/docs/daemon-cmd-runner.zh.md +5 -0
  8. package/dist/llm/kernel-driver/drive.js +382 -12
  9. package/dist/llm/kernel-driver/fbr.d.ts +9 -0
  10. package/dist/llm/kernel-driver/fbr.js +186 -59
  11. package/dist/llm/kernel-driver/flow.js +58 -56
  12. package/dist/llm/kernel-driver/reply-guidance.d.ts +3 -0
  13. package/dist/llm/kernel-driver/reply-guidance.js +15 -31
  14. package/dist/minds/load.js +1 -0
  15. package/dist/persistence.js +22 -1
  16. package/dist/runtime/driver-messages.d.ts +9 -0
  17. package/dist/runtime/driver-messages.js +61 -5
  18. package/dist/runtime/shared-reminder-update-impact.d.ts +20 -0
  19. package/dist/runtime/shared-reminder-update-impact.js +110 -0
  20. package/dist/server/dominds-runtime-status.js +23 -0
  21. package/dist/server/static-server.js +20 -5
  22. package/dist/tool-availability.js +1 -0
  23. package/dist/tools/builtins.js +2 -0
  24. package/dist/tools/cmd-runner-protocol.d.ts +6 -0
  25. package/dist/tools/cmd-runner-protocol.js +57 -2
  26. package/dist/tools/cmd-runner.js +83 -2
  27. package/dist/tools/ctrl.d.ts +2 -0
  28. package/dist/tools/ctrl.js +179 -5
  29. package/dist/tools/os.js +115 -14
  30. package/dist/tools/process-kill.js +49 -0
  31. package/dist/tools/prompts/control/en/errors.md +1 -1
  32. package/dist/tools/prompts/control/en/index.md +1 -1
  33. package/dist/tools/prompts/control/en/principles.md +18 -17
  34. package/dist/tools/prompts/control/en/tools.md +24 -1
  35. package/dist/tools/prompts/control/zh/errors.md +1 -1
  36. package/dist/tools/prompts/control/zh/index.md +1 -1
  37. package/dist/tools/prompts/control/zh/principles.md +17 -16
  38. package/dist/tools/prompts/control/zh/tools.md +24 -1
  39. package/dist/tools/prompts/os/en/tools.md +2 -0
  40. package/dist/tools/prompts/os/zh/tools.md +2 -0
  41. package/package.json +3 -3
  42. package/webapp/dist/assets/{main-APO6XREZ.js → main-NXVX2KTO.js} +24 -5
  43. package/webapp/dist/assets/{main-APO6XREZ.js.map → main-NXVX2KTO.js.map} +3 -3
  44. package/webapp/dist/index.html +1 -1
@@ -123,7 +123,9 @@ async function resolveTargetDialog(sourceDlg, target) {
123
123
  }
124
124
  return sideDialog;
125
125
  }
126
- const matches = targetRoot.getAllDialogs().filter((dialog) => dialog.agentId === target.agentId);
126
+ const matches = targetRoot
127
+ .getLoadedDialogTreeSnapshot()
128
+ .filter((dialog) => dialog.agentId === target.agentId);
127
129
  if (matches.length === 1) {
128
130
  return matches[0];
129
131
  }
@@ -1,4 +1,4 @@
1
- import { type MainDialog } from './dialog';
1
+ import { type Dialog, type MainDialog } from './dialog';
2
2
  export type DriveTriggerEvent = Readonly<{
3
3
  type: 'drive_trigger_evt';
4
4
  action: 'queue_root_drive' | 'clear_root_drive_queue' | 'active_run_cleared';
@@ -47,6 +47,16 @@ declare class GlobalDialogRegistry {
47
47
  noteActiveRunBlockedQueuedDrive(rootId: string): void;
48
48
  hasPendingActiveRunClearedDrive(rootId: string): boolean;
49
49
  isRootDriveQueued(rootId: string): boolean;
50
+ /**
51
+ * One-shot runtime snapshot for shared-reminder impact routing.
52
+ *
53
+ * This intentionally does not enumerate persisted "all running dialogs". It walks only the roots
54
+ * this runtime has already registered, and includes only dialogs with direct in-memory evidence
55
+ * of current work: locked running main dialogs and the loaded active-callee frontier under each
56
+ * root. Dialogs that merely remain in memory/history but have no current-work signal are not
57
+ * considered parallel work.
58
+ */
59
+ getLoadedInFlightDialogsForSharedReminderImpact(): Dialog[];
50
60
  getLastDriveTrigger(rootId: string): DriveTriggerEvent | undefined;
51
61
  consumeQueuedMainDialogs(): MainDialog[];
52
62
  get size(): number;
@@ -218,6 +218,51 @@ class GlobalDialogRegistry {
218
218
  isRootDriveQueued(rootId) {
219
219
  return this.entries.get(rootId)?.driveQueued === true;
220
220
  }
221
+ /**
222
+ * One-shot runtime snapshot for shared-reminder impact routing.
223
+ *
224
+ * This intentionally does not enumerate persisted "all running dialogs". It walks only the roots
225
+ * this runtime has already registered, and includes only dialogs with direct in-memory evidence
226
+ * of current work: locked running main dialogs and the loaded active-callee frontier under each
227
+ * root. Dialogs that merely remain in memory/history but have no current-work signal are not
228
+ * considered parallel work.
229
+ */
230
+ getLoadedInFlightDialogsForSharedReminderImpact() {
231
+ const dialogs = [];
232
+ for (const entry of this.entries.values()) {
233
+ const rootDialog = entry.mainDialog;
234
+ const visitedDialogIds = new Set();
235
+ const stack = [];
236
+ if (rootDialog.status === 'running' && rootDialog.isLocked()) {
237
+ dialogs.push(rootDialog);
238
+ visitedDialogIds.add(rootDialog.id.valueOf());
239
+ }
240
+ for (const calleeId of rootDialog.activeCalleeDialogIds) {
241
+ const loadedCallee = rootDialog.lookupDialog(calleeId.selfId);
242
+ if (loadedCallee && loadedCallee.status === 'running') {
243
+ stack.push(loadedCallee);
244
+ }
245
+ }
246
+ while (stack.length > 0) {
247
+ const current = stack.pop();
248
+ if (!current || visitedDialogIds.has(current.id.valueOf())) {
249
+ continue;
250
+ }
251
+ visitedDialogIds.add(current.id.valueOf());
252
+ if (current.status !== 'running') {
253
+ continue;
254
+ }
255
+ dialogs.push(current);
256
+ for (const calleeId of current.activeCalleeDialogIds) {
257
+ const loadedCallee = rootDialog.lookupDialog(calleeId.selfId);
258
+ if (loadedCallee && loadedCallee.status === 'running') {
259
+ stack.push(loadedCallee);
260
+ }
261
+ }
262
+ }
263
+ }
264
+ return dialogs;
265
+ }
221
266
  getLastDriveTrigger(rootId) {
222
267
  return this.lastDriveTriggerByRootId.get(rootId);
223
268
  }
package/dist/dialog.d.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  */
15
15
  import type { ContextHealthSnapshot } from '@longrun-ai/kernel/types/context-health';
16
16
  import type { DialogEvent, NativeToolCallPayload, ReminderContent, WebSearchCallAction, WebSearchCallSource } from '@longrun-ai/kernel/types/dialog';
17
- import type { DialogCalleeReplyTarget, DialogQueuedDeferredQ4HAnswerState, DialogQueuedPromptState, DialogQueuedUserGenerationBoundaryState, DialogRunControlSpec, DialogRuntimePrompt, DialogUserPrompt, DriveIntent } from '@longrun-ai/kernel/types/drive-intent';
17
+ import type { DialogCalleeReplyTarget, DialogQueuedDeferredQ4HAnswerState, DialogQueuedPromptState, DialogQueuedUserGenerationBoundaryState, DialogRunControlSpec, DialogRuntimePrompt, DialogRuntimeSideDialogPrompt, DialogUserPrompt, DriveIntent } from '@longrun-ai/kernel/types/drive-intent';
18
18
  import type { LanguageCode } from '@longrun-ai/kernel/types/language';
19
19
  import type { ActiveCalleesFile, CalleeCourseNumber, CalleeGenerationSeqNumber, CallSiteCourseNo, CallSiteGenseqNo, DialogAskerStackState, DialogLatestFile, DialogMetadataFile, DialogNextStepTrigger, HumanQuestion, ProviderData, ReasoningPayload, TellaskCallRecordName, TellaskReplyDirective } from '@longrun-ai/kernel/types/storage';
20
20
  import { ChatMessage, FuncResultMsg, TellaskCarryoverMsg, TellaskResultMsg } from './llm/client';
@@ -130,6 +130,7 @@ export declare function buildSideDialogAskerStack(args: {
130
130
  askerDialogId: string;
131
131
  assignment: AssignmentFromAsker;
132
132
  }): DialogAskerStackState;
133
+ export declare function buildSideDialogAssignmentPromptMeta(sideDialog: SideDialog): Pick<DialogRuntimeSideDialogPrompt, 'tellaskReplyDirective' | 'calleeDialogReplyTarget'>;
133
134
  /**
134
135
  * Abstract base class for all dialog types.
135
136
  * Contains common properties and methods shared between MainDialog and SideDialog.
@@ -274,6 +275,8 @@ export declare abstract class Dialog {
274
275
  private processReminderCollection;
275
276
  private buildVisibleReminderContents;
276
277
  private emitFullRemindersUpdate;
278
+ private reportReminderRuntimeFailure;
279
+ private dispatchSharedReminderOwnerUpdateImpacts;
277
280
  /**
278
281
  * Emits current visible reminders without reconciling tool-owned state or writing persistence.
279
282
  * This is for read-only dialog display, especially completed/archived dialogs opened from old tabs.
@@ -370,6 +373,13 @@ export declare abstract class Dialog {
370
373
  tellaskReplyDirective: TellaskReplyDirective;
371
374
  skipTaskdoc?: boolean;
372
375
  }): Promise<DialogQueuedPromptState>;
376
+ queueRuntimeGuidePrompt(options: {
377
+ prompt: string;
378
+ msgId: string;
379
+ grammar: 'markdown';
380
+ userLanguageCode?: LanguageCode;
381
+ skipTaskdoc?: boolean;
382
+ }): Promise<DialogQueuedPromptState>;
373
383
  queueRuntimeSideDialogPrompt(options: {
374
384
  prompt: string;
375
385
  msgId: string;
@@ -528,9 +538,9 @@ export declare class MainDialog extends Dialog {
528
538
  */
529
539
  lookupDialog(selfId: string): Dialog | undefined;
530
540
  /**
531
- * Get all registered dialogs in this dialog tree.
541
+ * Snapshot of dialogs loaded in this root's in-memory dialog tree.
532
542
  */
533
- getAllDialogs(): Dialog[];
543
+ getLoadedDialogTreeSnapshot(): Dialog[];
534
544
  /**
535
545
  * Remove a dialog from the local registry.
536
546
  */
package/dist/dialog.js CHANGED
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DialogStore = exports.MainDialog = exports.SideDialog = exports.Dialog = exports.DialogID = exports.InvalidReminderIndexError = void 0;
4
4
  exports.scheduleGlobalDialogMutexCleanupForRoot = scheduleGlobalDialogMutexCleanupForRoot;
5
5
  exports.buildSideDialogAskerStack = buildSideDialogAskerStack;
6
+ exports.buildSideDialogAssignmentPromptMeta = buildSideDialogAssignmentPromptMeta;
6
7
  const id_1 = require("@longrun-ai/kernel/utils/id");
7
8
  const time_1 = require("@longrun-ai/kernel/utils/time");
8
9
  const util_1 = require("util");
@@ -571,6 +572,7 @@ class Dialog {
571
572
  }
572
573
  async processReminderCollection(reminders) {
573
574
  let changed = false;
575
+ const updatedReminderIds = [];
574
576
  const indicesToRemove = [];
575
577
  for (let i = 0; i < reminders.length; i++) {
576
578
  const reminder = reminders[i];
@@ -598,26 +600,42 @@ class Dialog {
598
600
  priority: reminder.priority,
599
601
  renderMode: reminder.renderMode,
600
602
  });
601
- const contentChanged = updatedReminder.content !== reminder.content;
602
- const metaChanged = updatedReminder.meta !== reminder.meta;
603
- if (contentChanged || metaChanged) {
603
+ const reminderChanged = (0, tool_1.computeReminderRenderRevision)(updatedReminder) !==
604
+ (0, tool_1.computeReminderRenderRevision)(reminder);
605
+ if (reminderChanged) {
604
606
  reminders[i] = updatedReminder;
605
607
  changed = true;
608
+ updatedReminderIds.push(updatedReminder.id);
606
609
  }
607
610
  break;
608
611
  }
609
612
  case 'keep':
610
613
  break;
614
+ default: {
615
+ const _exhaustive = result.treatment;
616
+ return _exhaustive;
617
+ }
611
618
  }
612
619
  }
613
620
  catch (error) {
614
- log_1.log.error(`Error updating reminder from tool ${reminder.owner}:`, error);
621
+ const detail = `Reminder owner update failed ` +
622
+ `(rootId=${this.id.rootId}, selfId=${this.id.selfId}, course=${this.currentCourse}, ` +
623
+ `reminderId=${reminder.id}, owner=${reminder.owner.name}, taskDocPath=${this.taskDocPath})`;
624
+ await this.reportReminderRuntimeFailure(detail, error, {
625
+ rootId: this.id.rootId,
626
+ selfId: this.id.selfId,
627
+ course: this.currentCourse,
628
+ reminderId: reminder.id,
629
+ ownerName: reminder.owner.name,
630
+ taskDocPath: this.taskDocPath,
631
+ });
632
+ throw error;
615
633
  }
616
634
  }
617
635
  for (let i = indicesToRemove.length - 1; i >= 0; i--) {
618
636
  reminders.splice(indicesToRemove[i], 1);
619
637
  }
620
- return changed;
638
+ return { changed, updatedReminderIds };
621
639
  }
622
640
  buildVisibleReminderContents(taskSharedReminders, runtimeReminders) {
623
641
  const visibleReminders = [
@@ -643,6 +661,32 @@ class Dialog {
643
661
  };
644
662
  (0, evt_registry_1.postDialogEvent)(this, fullRemindersEvt);
645
663
  }
664
+ async reportReminderRuntimeFailure(detail, error, context) {
665
+ try {
666
+ await this.streamError(detail);
667
+ }
668
+ catch (streamError) {
669
+ log_1.log.warn('Failed to emit stream_error_evt for reminder runtime failure', streamError, {
670
+ ...context,
671
+ streamErrorMessage: streamError instanceof Error ? streamError.message : String(streamError),
672
+ });
673
+ }
674
+ log_1.log.error(detail, error, {
675
+ ...context,
676
+ errorMessage: error instanceof Error ? error.message : String(error),
677
+ });
678
+ }
679
+ async dispatchSharedReminderOwnerUpdateImpacts(reminderIds, scope) {
680
+ const { dispatchSharedReminderUpdateImpact } = await import('./runtime/shared-reminder-update-impact.js');
681
+ for (const reminderId of reminderIds) {
682
+ await dispatchSharedReminderUpdateImpact({
683
+ updater: this,
684
+ reminderId,
685
+ scope,
686
+ language: (0, work_language_1.getWorkLanguage)(),
687
+ });
688
+ }
689
+ }
646
690
  /**
647
691
  * Emits current visible reminders without reconciling tool-owned state or writing persistence.
648
692
  * This is for read-only dialog display, especially completed/archived dialogs opened from old tabs.
@@ -674,10 +718,10 @@ class Dialog {
674
718
  const runtimeTarget = { kind: 'agent', agentId: this.agentId };
675
719
  const taskSharedReminders = await (0, shared_reminders_1.loadSharedReminders)(taskSharedTarget);
676
720
  const runtimeReminders = await (0, shared_reminders_1.loadSharedReminders)(runtimeTarget);
677
- const localChanged = await this.processReminderCollection(this.reminders);
678
- const taskSharedChanged = await this.processReminderCollection(taskSharedReminders);
679
- const runtimeChanged = await this.processReminderCollection(runtimeReminders);
680
- if (localChanged || taskSharedChanged || runtimeChanged) {
721
+ const localResult = await this.processReminderCollection(this.reminders);
722
+ const taskSharedResult = await this.processReminderCollection(taskSharedReminders);
723
+ const runtimeResult = await this.processReminderCollection(runtimeReminders);
724
+ if (localResult.changed || taskSharedResult.changed || runtimeResult.changed) {
681
725
  this.touchReminders();
682
726
  }
683
727
  // Centralized persistence - called when emitting event.
@@ -686,26 +730,55 @@ class Dialog {
686
730
  await this.dlgStore.persistReminders(this, this.reminders);
687
731
  }
688
732
  catch (err) {
689
- log_1.log.warn('Failed to persist reminders', err, { dialogId: this.id.valueOf() });
733
+ const detail = `Failed to persist dialog-local reminders ` +
734
+ `(rootId=${this.id.rootId}, selfId=${this.id.selfId}, course=${this.currentCourse})`;
735
+ await this.reportReminderRuntimeFailure(detail, err, {
736
+ rootId: this.id.rootId,
737
+ selfId: this.id.selfId,
738
+ course: this.currentCourse,
739
+ taskDocPath: this.taskDocPath,
740
+ });
741
+ throw err;
690
742
  }
691
743
  try {
692
744
  await (0, shared_reminders_1.replaceSharedReminders)(taskSharedTarget, taskSharedReminders);
693
745
  }
694
746
  catch (err) {
695
- log_1.log.warn('Failed to persist task-scoped reminders', err, {
696
- dialogId: this.id.valueOf(),
747
+ const detail = `Failed to persist task-scoped reminders ` +
748
+ `(rootId=${this.id.rootId}, selfId=${this.id.selfId}, course=${this.currentCourse}, ` +
749
+ `agentId=${this.agentId}, taskDocPath=${this.taskDocPath})`;
750
+ await this.reportReminderRuntimeFailure(detail, err, {
751
+ rootId: this.id.rootId,
752
+ selfId: this.id.selfId,
753
+ course: this.currentCourse,
697
754
  agentId: this.agentId,
698
755
  taskDocPath: this.taskDocPath,
699
756
  });
757
+ throw err;
700
758
  }
759
+ await this.dispatchSharedReminderOwnerUpdateImpacts(taskSharedResult.updatedReminderIds, 'task');
701
760
  try {
702
761
  await (0, shared_reminders_1.replaceSharedReminders)(runtimeTarget, runtimeReminders);
703
762
  }
704
763
  catch (err) {
705
- log_1.log.warn('Failed to persist agent-scoped reminders', err, {
706
- dialogId: this.id.valueOf(),
764
+ const detail = `Failed to persist agent-scoped reminders ` +
765
+ `(rootId=${this.id.rootId}, selfId=${this.id.selfId}, course=${this.currentCourse}, ` +
766
+ `agentId=${this.agentId})`;
767
+ await this.reportReminderRuntimeFailure(detail, err, {
768
+ rootId: this.id.rootId,
769
+ selfId: this.id.selfId,
770
+ course: this.currentCourse,
707
771
  agentId: this.agentId,
772
+ taskDocPath: this.taskDocPath,
708
773
  });
774
+ throw err;
775
+ }
776
+ for (const reminderId of runtimeResult.updatedReminderIds) {
777
+ const reminder = runtimeReminders.find((candidate) => candidate.id === reminderId);
778
+ if (!reminder) {
779
+ throw new Error(`Updated shared reminder ${reminderId} disappeared before update impact dispatch`);
780
+ }
781
+ await this.dispatchSharedReminderOwnerUpdateImpacts([reminderId], reminder.scope === 'runtime' ? 'runtime' : 'agent');
709
782
  }
710
783
  const reminders = this.buildVisibleReminderContents(taskSharedReminders, runtimeReminders);
711
784
  this.emitFullRemindersUpdate(reminders);
@@ -1167,6 +1240,24 @@ class Dialog {
1167
1240
  });
1168
1241
  return created;
1169
1242
  }
1243
+ async queueRuntimeGuidePrompt(options) {
1244
+ const common = this.runtimePromptCommon(options);
1245
+ const created = {
1246
+ ...common,
1247
+ kind: 'new_course_runtime_guide',
1248
+ skipTaskdoc: options.skipTaskdoc,
1249
+ };
1250
+ this.enqueueQueuedPromptState(created);
1251
+ await this.persistPendingRuntimePrompt({
1252
+ content: created.prompt,
1253
+ msgId: created.msgId,
1254
+ grammar: created.grammar ?? 'markdown',
1255
+ userLanguageCode: created.userLanguageCode,
1256
+ origin: 'runtime',
1257
+ skipTaskdoc: created.skipTaskdoc,
1258
+ });
1259
+ return created;
1260
+ }
1170
1261
  async queueRuntimeSideDialogPrompt(options) {
1171
1262
  const common = this.runtimePromptCommon(options);
1172
1263
  const created = {
@@ -1859,7 +1950,7 @@ exports.SideDialog = SideDialog;
1859
1950
  */
1860
1951
  class MainDialog extends Dialog {
1861
1952
  _status = 'running';
1862
- // Tracks all dialogs in this dialog tree for O(1) lookup
1953
+ // Tracks dialogs loaded into this root's in-memory dialog tree for O(1) lookup
1863
1954
  _localRegistry = new Map();
1864
1955
  // Tracks Type-B registered sideDialogs by agentId!sessionSlug
1865
1956
  _sideDialogRegistry = new Map();
@@ -1886,9 +1977,9 @@ class MainDialog extends Dialog {
1886
1977
  return this._localRegistry.get(selfId);
1887
1978
  }
1888
1979
  /**
1889
- * Get all registered dialogs in this dialog tree.
1980
+ * Snapshot of dialogs loaded in this root's in-memory dialog tree.
1890
1981
  */
1891
- getAllDialogs() {
1982
+ getLoadedDialogTreeSnapshot() {
1892
1983
  return Array.from(this._localRegistry.values());
1893
1984
  }
1894
1985
  /**
@@ -115,6 +115,8 @@ Ownership boundaries are simple:
115
115
  - `pid: number`
116
116
  - `stdout?: boolean`
117
117
  - `stderr?: boolean`
118
+ - `wait_for_new_output?: boolean`
119
+ - `timeout_ms?: number`
118
120
 
119
121
  ### Semantics
120
122
 
@@ -123,6 +125,9 @@ Ownership boundaries are simple:
123
125
  - output order is always `stdout` first, then `stderr`
124
126
  - each stream gets its own heading, content block, and scroll notice
125
127
  - unrequested streams are omitted entirely
128
+ - when `wait_for_new_output=true`, the runner waits until at least one requested stream receives output after the request is accepted
129
+ - if `timeout_ms` is provided, the wait is bounded by that many milliseconds, capped at `86400000` (24h), and returns the current snapshot with an explicit timeout notice
130
+ - `timeout_ms` cannot be combined with `wait_for_new_output=false`; that contradictory argument set returns an error
126
131
 
127
132
  ### Why this is better
128
133
 
@@ -114,6 +114,8 @@ runner 自己作为进程组 leader,daemon 默认继承该 pgid。这样 `stop
114
114
  - `pid: number`
115
115
  - `stdout?: boolean`
116
116
  - `stderr?: boolean`
117
+ - `wait_for_new_output?: boolean`
118
+ - `timeout_ms?: number`
117
119
 
118
120
  ### 语义
119
121
 
@@ -122,6 +124,9 @@ runner 自己作为进程组 leader,daemon 默认继承该 pgid。这样 `stop
122
124
  - 返回顺序固定为 `stdout` 在前、`stderr` 在后
123
125
  - 每个流各自带自己的标题、内容、scroll notice
124
126
  - 未请求的流不展示
127
+ - `wait_for_new_output=true` 时,runner 会等到请求的流中至少一个在请求被接受后出现新输出再返回
128
+ - 提供 `timeout_ms` 时,等待最多持续指定毫秒数,上限为 `86400000`(24 小时);超时后返回当前快照,并显式提示等待超时
129
+ - `timeout_ms` 不可与 `wait_for_new_output=false` 同时使用;这种矛盾参数会直接报错
125
130
 
126
131
  ### 取舍理由
127
132