@semalt-ai/code 1.8.0 → 1.8.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.
@@ -8,7 +8,6 @@ const TIER_SYS = ['system_info', 'get_env', 'set_env'];
8
8
  const TIER_MAP = { fs: TIER_FS, exec: TIER_EXEC, net: TIER_NET, sys: TIER_SYS };
9
9
  const READONLY_BLOCKED = new Set(['write_file', 'append_file', 'delete_file', 'move_file', 'copy_file']);
10
10
 
11
- let _permissionCounter = 0;
12
11
  let _permissionQueueTail = Promise.resolve();
13
12
 
14
13
  function createPermissionManager(ui, { allowedTiers = [], readonly = false } = {}) {
@@ -21,8 +20,8 @@ function createPermissionManager(ui, { allowedTiers = [], readonly = false } = {
21
20
  }
22
21
 
23
22
  const state = {
24
- autoApproveShell: false,
25
- autoApproveFile: false,
23
+ autoApproveAll: false,
24
+ sessionApprovedTags: new Set(),
26
25
  };
27
26
 
28
27
  let uiCallbacks = null;
@@ -40,92 +39,96 @@ function createPermissionManager(ui, { allowedTiers = [], readonly = false } = {
40
39
 
41
40
  const MAX_DESC_LINES = 12;
42
41
 
43
- function requestPermission(description, onAddMessage, onRerenderMessage, onCollapseMessage, onCaptureNavigation) {
42
+ // The picker renders into the writer's modal region — a live band above
43
+ // the status bar that redraws in place on every keystroke. Arrow-key
44
+ // navigation rebuilds the lines array and calls onShowModal again; nothing
45
+ // lands in scrollback until the user confirms. On resolve/cancel the
46
+ // modal is cleared and a single summary line is emitted to scrollback
47
+ // (for multi-line descriptions — e.g. a file-write diff — the full body
48
+ // is retained so the user can still see what was approved).
49
+ function requestPermission(description, onShowModal, onCloseModal, onCaptureNavigation) {
44
50
  // Serialize dialogs: each permission waits for the previous one to be answered
45
51
  const myTurn = _permissionQueueTail;
46
52
  let releaseQueue;
47
53
  _permissionQueueTail = new Promise((r) => { releaseQueue = r; });
48
54
 
49
55
  return myTurn.then(() => new Promise((resolve) => {
50
- const uniqueId = `perm_${++_permissionCounter}`;
51
56
  const options = ['Yes', 'Always', 'No'];
52
57
  let selectedIdx = 0;
53
58
  const descLines = description.split('\n');
54
59
  const truncatable = descLines.length > MAX_DESC_LINES;
55
60
 
56
- function buildContent() {
57
- let visibleDesc;
58
- if (truncatable) {
59
- visibleDesc = descLines.slice(0, MAX_DESC_LINES).join('\n') +
60
- `\n \x1b[2m… ${descLines.length - MAX_DESC_LINES} more lines\x1b[0m`;
61
- } else {
62
- visibleDesc = description;
61
+ function buildModalLines() {
62
+ const lines = [];
63
+ const visible = truncatable
64
+ ? descLines.slice(0, MAX_DESC_LINES).concat([` \x1b[2m… ${descLines.length - MAX_DESC_LINES} more lines\x1b[0m`])
65
+ : descLines;
66
+ // First description line gets the bullet glyph; continuation lines
67
+ // are indented to align under it. Matches the pre-modal rendering
68
+ // that went through chatHistory's system-message renderer.
69
+ const first = visible[0] || '';
70
+ lines.push(` \x1b[38;5;244m●\x1b[0m \x1b[38;5;244m${first}\x1b[0m`);
71
+ for (let i = 1; i < visible.length; i++) {
72
+ lines.push(` \x1b[38;5;244m${visible[i]}\x1b[0m`);
63
73
  }
64
- const parts = [visibleDesc, ''];
74
+ lines.push('');
65
75
  for (let i = 0; i < options.length; i++) {
66
- parts.push(i === selectedIdx
76
+ lines.push(i === selectedIdx
67
77
  ? `\x1b[1m\x1b[36m ► ${options[i]}\x1b[0m`
68
78
  : ` ${options[i]}`
69
79
  );
70
80
  }
71
- return parts.join('\n');
81
+ return lines;
72
82
  }
73
83
 
74
- const permMsg = { role: 'system', id: uniqueId, content: buildContent() };
75
- onAddMessage(permMsg);
84
+ onShowModal(buildModalLines());
85
+
86
+ function finish(result) {
87
+ const chosen = result === 'cancel' ? 'no' : options[selectedIdx].toLowerCase();
88
+ const glyph = (chosen === 'no') ? '✗' : '✓';
89
+ // The full `description` is preserved in the summary so multi-line
90
+ // bodies (e.g. file-write diffs) remain visible in scrollback after
91
+ // the modal closes. chatHistory's system-message renderer styles the
92
+ // first line by the leading glyph and indents continuations.
93
+ onCloseModal(`${glyph} ${description}`);
94
+ releaseQueue();
95
+ resolve(chosen);
96
+ }
76
97
 
77
98
  const releaseNav = onCaptureNavigation((action) => {
78
99
  if (action === 'next') {
79
100
  selectedIdx = (selectedIdx + 1) % options.length;
80
- permMsg.content = buildContent();
81
- onRerenderMessage(uniqueId);
101
+ onShowModal(buildModalLines());
82
102
  } else if (action === 'prev') {
83
103
  selectedIdx = (selectedIdx - 1 + options.length) % options.length;
84
- permMsg.content = buildContent();
85
- onRerenderMessage(uniqueId);
104
+ onShowModal(buildModalLines());
86
105
  } else if (action === 'select') {
87
- const chosen = options[selectedIdx];
88
106
  releaseNav();
89
- permMsg.content = chosen === 'No' ? `✗ ${description}` : `✓ ${description}`;
90
- onCollapseMessage(uniqueId);
91
- releaseQueue();
92
- resolve(chosen.toLowerCase());
107
+ finish('select');
93
108
  } else if (action === 'cancel') {
94
109
  releaseNav();
95
- permMsg.content = `✗ ${description}`;
96
- onCollapseMessage(uniqueId);
97
- releaseQueue();
98
- resolve('no');
110
+ finish('cancel');
99
111
  }
100
112
  });
101
113
  }));
102
114
  }
103
115
 
104
- async function askPermission(actionType, description, tag) {
105
- if (tag && autoApprovedTags.has(tag)) {
106
- if (uiCallbacks) {
107
- uiCallbacks.onAddMessage({ role: 'system', content: `✓ Auto-approved: ${description}` });
108
- } else {
109
- console.log(` ${FG_GREEN}✓${RST} ${FG_DARK}Auto-approved: ${description}${RST}`);
110
- }
111
- return true;
116
+ function _emitAutoApproved(description) {
117
+ if (uiCallbacks) {
118
+ uiCallbacks.onAddMessage({ role: 'system', content: `✓ Auto-approved: ${description}` });
119
+ } else {
120
+ console.log(` ${FG_GREEN}✓${RST} ${FG_DARK}Auto-approved: ${description}${RST}`);
112
121
  }
122
+ }
113
123
 
114
- if (actionType === 'shell' && state.autoApproveShell) {
115
- if (uiCallbacks) {
116
- uiCallbacks.onAddMessage({ role: 'system', content: `✓ Auto-approved: ${description}` });
117
- } else {
118
- console.log(` ${FG_GREEN}✓${RST} ${FG_DARK}Auto-approved: ${description}${RST}`);
119
- }
124
+ async function askPermission(actionType, description, tag) {
125
+ if (state.autoApproveAll) {
126
+ _emitAutoApproved(description);
120
127
  return true;
121
128
  }
122
129
 
123
- if (actionType === 'file' && state.autoApproveFile) {
124
- if (uiCallbacks) {
125
- uiCallbacks.onAddMessage({ role: 'system', content: `✓ Auto-approved: ${description}` });
126
- } else {
127
- console.log(` ${FG_GREEN}✓${RST} ${FG_DARK}Auto-approved: ${description}${RST}`);
128
- }
130
+ if (tag && (autoApprovedTags.has(tag) || state.sessionApprovedTags.has(tag))) {
131
+ _emitAutoApproved(description);
129
132
  return true;
130
133
  }
131
134
 
@@ -137,25 +140,23 @@ function createPermissionManager(ui, { allowedTiers = [], readonly = false } = {
137
140
  if (uiCallbacks) {
138
141
  const result = await requestPermission(
139
142
  `${actionType}: ${description}`,
140
- uiCallbacks.onAddMessage,
141
- uiCallbacks.onRerenderMessage,
142
- uiCallbacks.onCollapseMessage,
143
+ uiCallbacks.onShowModal,
144
+ uiCallbacks.onCloseModal,
143
145
  uiCallbacks.onCaptureNavigation,
144
146
  );
145
147
 
146
148
  if (result === 'no') return false;
147
149
 
148
- if (result === 'always') {
149
- if (actionType === 'shell') state.autoApproveShell = true;
150
- else state.autoApproveFile = true;
151
- uiCallbacks.onAddMessage({ role: 'system', content: `✓ Auto-approve enabled for ${actionType} operations` });
150
+ if (result === 'always' && tag) {
151
+ state.sessionApprovedTags.add(tag);
152
+ uiCallbacks.onAddMessage({ role: 'system', content: `✓ Auto-approve enabled for \`${tag}\` this session` });
152
153
  }
153
154
 
154
155
  return true;
155
156
  }
156
157
 
157
158
  // Fallback: legacy TTY interactive select (used outside of chat UI)
158
- const alwaysLabel = actionType === 'shell' ? 'Yes, always for shell' : 'Yes, always for files';
159
+ const alwaysLabel = tag ? `Yes, always for <${tag}>` : 'Yes, always';
159
160
  const choices = ['Yes', alwaysLabel, 'No'];
160
161
 
161
162
  console.log();
@@ -177,10 +178,9 @@ function createPermissionManager(ui, { allowedTiers = [], readonly = false } = {
177
178
  return false;
178
179
  }
179
180
 
180
- if (selectedIndex === 1) {
181
- if (actionType === 'shell') state.autoApproveShell = true;
182
- else state.autoApproveFile = true;
183
- console.log(` ${FG_GREEN}✓${RST} ${FG_DARK}Auto-approve enabled for ${actionType} operations${RST}`);
181
+ if (selectedIndex === 1 && tag) {
182
+ state.sessionApprovedTags.add(tag);
183
+ console.log(` ${FG_GREEN}✓${RST} ${FG_DARK}Auto-approve enabled for <${tag}> this session${RST}`);
184
184
  }
185
185
 
186
186
  return true;
@@ -194,14 +194,13 @@ function createPermissionManager(ui, { allowedTiers = [], readonly = false } = {
194
194
  }
195
195
 
196
196
  function clear() {
197
- state.autoApproveShell = false;
198
- state.autoApproveFile = false;
197
+ state.autoApproveAll = false;
198
+ state.sessionApprovedTags.clear();
199
199
  }
200
200
 
201
201
  function toggleAll() {
202
- state.autoApproveShell = !state.autoApproveShell;
203
- state.autoApproveFile = !state.autoApproveFile;
204
- return state.autoApproveShell;
202
+ state.autoApproveAll = !state.autoApproveAll;
203
+ return state.autoApproveAll;
205
204
  }
206
205
 
207
206
  return {
package/lib/prompts.js CHANGED
@@ -1,105 +1,119 @@
1
1
  'use strict';
2
2
 
3
- const SYSTEM_PROMPT = `You are Semalt.AI, an expert AI coding assistant running in the user's terminal. You have the ability to execute shell commands and file operations.
4
-
5
- IMPORTANT: You CAN execute commands on the user's system. When you need to run a command, use this exact format:
6
-
7
- To run a shell command:
8
- <exec>command here</exec>
9
-
10
- To read a file:
11
- <read_file>/path/to/file</read_file>
12
-
13
- To write a file (also accepted as <create_file path="...">...</create_file>):
14
- <write_file path="/path/to/file">file content here</write_file>
15
-
16
- To append content to an existing file:
17
- <append_file path="/path/to/file">content to append</append_file>
18
-
19
- To delete a file:
20
- <delete_file>/path/to/file</delete_file>
21
-
22
- To list a directory (entries marked [F] file, [D] dir, [L] symlink):
23
- <list_dir>/path/to/dir</list_dir>
24
-
25
- To create a directory (including parents):
26
- <make_dir>/path/to/new/dir</make_dir>
27
-
28
- To remove a directory and all its contents:
29
- <remove_dir>/path/to/dir</remove_dir>
30
-
31
- To move or rename a file:
32
- <move_file src="/old/path" dst="/new/path"></move_file>
33
-
34
- To copy a file:
35
- <copy_file src="/source/path" dst="/destination/path"></copy_file>
36
-
37
- To get file metadata (size, type, permissions, modification time):
38
- <file_stat>/path/to/file</file_stat>
39
-
40
- To search for files matching a glob pattern (supports * and **):
41
- <search_files pattern="**/*.js" dir="/path/to/search"></search_files>
42
-
43
- To search for a regex pattern within a file (returns matching lines):
44
- <search_in_file path="/path/to/file">regex pattern</search_in_file>
3
+ const { TAG_REGISTRY } = require('./constants');
4
+
5
+ const WRAPPER_NAMES = new Set([
6
+ 'minimax:tool_call',
7
+ 'qwen:tool_call',
8
+ 'invoke',
9
+ 'parameter',
10
+ 'tool_call',
11
+ 'function_call',
12
+ ]);
13
+
14
+ // For each tool tag: required attributes and a one-line purpose.
15
+ // Required attributes are derived from the matchers in `extractToolCalls`
16
+ // (lib/tools.js). Where a tag accepts either an attribute or inline content,
17
+ // the attribute is marked optional.
18
+ const TOOL_TAG_SPECS = {
19
+ exec: { attrs: [], purpose: 'Run a shell command (inline content).' },
20
+ shell: { attrs: [], purpose: 'Run a shell command (inline content).' },
21
+ read_file: { attrs: ['path?'], purpose: 'Read a file (path attr or inline content).' },
22
+ write_file: { attrs: ['path'], purpose: 'Write file with inline content (overwrites).' },
23
+ create_file: { attrs: ['path'], purpose: 'Create file with inline content.' },
24
+ append_file: { attrs: ['path'], purpose: 'Append inline content to file.' },
25
+ delete_file: { attrs: [], purpose: 'Delete a file (inline content = path).' },
26
+ list_dir: { attrs: [], purpose: 'List directory contents (inline content = path).' },
27
+ make_dir: { attrs: [], purpose: 'Create directory recursively (inline content = path).' },
28
+ remove_dir: { attrs: [], purpose: 'Remove directory recursively (inline content = path).' },
29
+ move_file: { attrs: ['src', 'dst'], purpose: 'Move or rename a file.' },
30
+ copy_file: { attrs: ['src', 'dst'], purpose: 'Copy a file.' },
31
+ file_stat: { attrs: [], purpose: 'Stat a file (inline content = path).' },
32
+ edit_file: { attrs: ['path', 'line'], purpose: 'Replace a single line in a file (inline content = new line).' },
33
+ search_files: { attrs: ['pattern?', 'dir?'], purpose: 'Find files by glob pattern.' },
34
+ search_in_file: { attrs: ['path'], purpose: 'Regex search inside a file (inline content = pattern).' },
35
+ replace_in_file: { attrs: ['path', 'search', 'replace'], purpose: 'Regex replace inside a file.' },
36
+ get_env: { attrs: [], purpose: 'Read an env var (inline content = name).' },
37
+ set_env: { attrs: ['name', 'value'], purpose: 'Set an env var for this process.' },
38
+ download: { attrs: [], purpose: 'HTTP download to the CWD (inline content = URL).' },
39
+ upload: { attrs: ['path'], purpose: 'Write base64-encoded content to file.' },
40
+ http_get: { attrs: ['url'], purpose: 'HTTP GET; returns the response body (truncated to a byte cap with an explicit notice when oversized).' },
41
+ ask_user: { attrs: ['question'], purpose: 'Ask the user a question and receive an answer.' },
42
+ store_memory: { attrs: ['key'], purpose: 'Persist a key/value to local memory (inline content = value).' },
43
+ recall_memory: { attrs: ['key'], purpose: 'Read a key from local memory.' },
44
+ list_memories: { attrs: [], purpose: 'List memory keys.' },
45
+ system_info: { attrs: [], purpose: 'Return platform, arch, host, memory, node version, cwd.' },
46
+ };
45
47
 
46
- To replace text in a file using a regex pattern:
47
- <replace_in_file path="/path/to/file" search="old pattern" replace="new text"></replace_in_file>
48
+ function buildTagInventory() {
49
+ const lines = [];
50
+ for (const tag of Object.keys(TAG_REGISTRY)) {
51
+ const entry = TAG_REGISTRY[tag];
52
+ if (entry.type !== 'tool') continue;
53
+ if (WRAPPER_NAMES.has(tag)) continue;
54
+ const spec = TOOL_TAG_SPECS[tag] || { attrs: [], purpose: '' };
55
+ const attrPart = spec.attrs.length
56
+ ? ` [attrs: ${spec.attrs.join(', ')}]`
57
+ : '';
58
+ lines.push(`- <${tag}>${attrPart} — ${spec.purpose}`);
59
+ }
60
+ return lines.join('\n');
61
+ }
48
62
 
49
- To replace a specific line in a file:
50
- <edit_file path="/path/to/file" line="42">replacement line content</edit_file>
63
+ const TAG_INVENTORY = buildTagInventory();
51
64
 
52
- To fetch a URL over HTTP or HTTPS (HTML pages are auto-converted to plain text):
53
- <http_get url="https://example.com/api/data"></http_get>
65
+ const SYSTEM_PROMPT_TEMPLATE = `You are Semalt.AI, an expert AI coding assistant running in the user's terminal. You have the ability to execute shell commands and file operations.
54
66
 
55
- To fetch raw HTML when you need to parse markup, extract links, or inspect structure:
56
- <http_get url="https://example.com/" raw="true"></http_get>
67
+ ## Available tool tags:
57
68
 
58
- If the response is large it will be delivered in numbered parts. To retrieve the next part:
59
- <http_get_next key="https://example.com/api/data"/>
69
+ ${TAG_INVENTORY}
60
70
 
61
- To download a file from a URL (saved to current directory):
62
- <download>https://example.com/file.zip</download>
71
+ ## Tool call syntax use EXACTLY these forms:
63
72
 
64
- To write base64-encoded content to a file:
65
- <upload path="/path/to/file">base64encodedcontent</upload>
73
+ - Single-value inline-content tags: put the value directly between the tags as plain text. **Do NOT nest pseudo-tags like \`<path>\`, \`<command>\`, \`<url>\`, \`<key>\`, \`<name>\`, \`<pattern>\`, or \`<question>\` inside the body** — the parser treats the body as the literal value.
74
+ - Correct: \`<list_dir>/tmp/foo</list_dir>\`, \`<shell>ls -la</shell>\`, \`<read_file>/etc/hosts</read_file>\`, \`<download>https://x.com/f.zip</download>\`
75
+ - Wrong: \`<list_dir><path>/tmp/foo</path></list_dir>\`, \`<shell><command>ls</command></shell>\`
76
+ - Attribute tags: parameters go as quoted attributes on the opening tag; the body is either empty (self-closed) or real payload content (e.g. file bytes for \`write_file\`).
77
+ - Correct: \`<write_file path="/tmp/a.txt">actual file contents here</write_file>\`, \`<http_get url="https://example.com"/>\`, \`<move_file src="/a" dst="/b"/>\`
78
+ - Wrong: \`<write_file><path>/tmp/a.txt</path><content>...</content></write_file>\`
66
79
 
67
- To ask the user a question and receive their typed answer:
68
- <ask_user question="Which directory should I use?"></ask_user>
80
+ ## Reasoning vs planning IMPORTANT:
69
81
 
70
- To store a value in persistent memory across sessions:
71
- <store_memory key="project_name">my-app</store_memory>
82
+ - Your internal chain-of-thought reasoning uses your native \`<think>...</think>\` block. Use it normally for deliberation. Do NOT treat \`<think>\` as a user-facing tool and do NOT try to emit \`<think>\` as an action — it is reserved for your own reasoning and is handled by the runtime.
83
+ - When you need to explicitly record a short plan that the agent framework can see (for logging or hand-off between steps), use \`<plan>...</plan>\` instead. \`<plan>\` is a tool tag; \`<think>\` is not.
84
+ - Never emit \`<think>\` as an action. The valid action tags are the ones listed above.
72
85
 
73
- To retrieve a previously stored memory value:
74
- <recall_memory key="project_name"></recall_memory>
86
+ ## STRICT RULES follow exactly:
75
87
 
76
- To list all keys stored in persistent memory:
77
- <list_memories></list_memories>
88
+ 1. Emit tool tags as needed. If a step depends on a prior tool's result, wait for the result before the next call. Independent tool calls may be batched in a single response.
89
+ 2. **Always end your response with exactly one action tag** whenever the task is not yet complete. Do NOT end a response with only prose like "Let me create the files…" — that prose must be immediately followed by the actual tool tag in the same response. Preamble without a tool tag is a bug.
90
+ 3. Only use the exact tag names listed above. Unknown tag names will not be executed.
91
+ 4. When the user asks you to do something on their system, USE the tools — do NOT just print instructions.
92
+ 5. Each action will be shown to the user for approval before execution.
93
+ 6. After execution you will receive the result and can proceed with the next action in a new response.
94
+ 7. Be concise. Provide working solutions. Use markdown for code blocks in explanations.
95
+ 8. Current working directory: __CWD__
78
96
 
79
- To get system information (OS, CPU arch, memory, hostname, user):
80
- <system_info></system_info>
97
+ Response contract (strict):
98
+ Every response must end with exactly one of:
99
+ (a) a tool call, using one of the XML tags listed above; OR
100
+ (b) <final_answer>...</final_answer> containing your answer to the user.
101
+ A response containing neither is invalid and will be rejected.
102
+ Do not describe actions in prose outside these two forms. Either call the tool, or wrap your final reply in <final_answer>.`;
81
103
 
82
- To read an environment variable:
83
- <get_env>VARIABLE_NAME</get_env>
104
+ const NATIVE_SYSTEM_PROMPT_TEMPLATE = `You are Semalt.AI, an expert AI coding assistant running in the user's terminal. Use the provided tools to execute shell commands and file operations; do not just print instructions. Each call is approved by the user before execution, and the result is returned to you for the next step.
84
105
 
85
- To set an environment variable for the current session:
86
- <set_env name="VARIABLE_NAME" value="value"/>
106
+ Use \`<think>...</think>\` for internal reasoning (runtime-handled; never emit as an action). Use \`<plan>...</plan>\` to record a short plan for the agent framework.
87
107
 
88
- To reason or think before acting (hidden from user by default):
89
- <think>your reasoning here</think>
108
+ Be concise. Use markdown for code blocks in explanations. Current working directory: __CWD__
90
109
 
91
- ## STRICT RULES follow exactly:
110
+ Response contract: every response must end with either (a) one or more tool calls, or (b) <final_answer>...</final_answer> containing your answer to the user. Prose outside those two forms is invalid.`;
92
111
 
93
- 1. **ONE tool tag per response, no exceptions.** After emitting a tag, stop. Wait for the result. Then emit the next tag in your next response. Never put two or more action tags in the same response.
94
- 2. Only use the exact tag names listed above. Unknown tag names will not be executed.
95
- 3. When the user asks you to do something on their system, USE the tools — do NOT just print instructions.
96
- 4. Each action will be shown to the user for approval before execution.
97
- 5. After execution you will receive the result and can proceed.
98
- 6. Be concise. Provide working solutions. Use markdown for code blocks in explanations.
99
- 7. **Permission denied fallback:** If any tool action (write_file, delete_file, make_dir, etc.) is denied or returns a permission error, immediately retry the same operation using \`<exec>\` with an equivalent shell command (e.g. \`tee\`, \`rm\`, \`mkdir -p\`, \`cp\`, \`mv\`). Never give up after a single denial — the shell is always the fallback.
100
- 8. If a shell command itself also fails with a permission error, immediately retry it prefixed with \`sudo\` (e.g. \`sudo tee /path\`, \`sudo mkdir -p /path\`). Do not ask — just try. Only stop and explain if sudo itself fails.
101
- 9. Current working directory: ${process.cwd()}`;
112
+ function getSystemPrompt(nativeTools = false) {
113
+ const template = nativeTools ? NATIVE_SYSTEM_PROMPT_TEMPLATE : SYSTEM_PROMPT_TEMPLATE;
114
+ return template.replace('__CWD__', process.cwd());
115
+ }
102
116
 
103
117
  module.exports = {
104
- SYSTEM_PROMPT,
118
+ getSystemPrompt,
105
119
  };