aegiscode 6.5.2 → 6.5.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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "aegiscode",
3
3
  "productName": "AEGIS Code",
4
- "version": "6.5.2",
4
+ "version": "6.5.4",
5
5
  "description": "aegiscode \u2014 the command-line version of AEGIS Desktop. The shared tool surface in your shell, over the same thin transport and tool registry as the MCP plugin and the desktop app. Ships transport + UI only; no brain.",
6
6
  "author": {
7
7
  "name": "AEGIS Code",
package/src/app.js CHANGED
@@ -21,7 +21,7 @@
21
21
  const readline = require('node:readline');
22
22
  const os = require('node:os');
23
23
  const { randomUUID } = require('node:crypto');
24
- const { createTools, createClient, usageTokens } = require('./deps.js');
24
+ const { createTools, createClient, usageTokens, buildSystemPrompt } = require('./deps.js');
25
25
  const { createEngine } = require('./engine.js');
26
26
  const { GLYPH, VERBS, themeOf, RESET, THEME_TABLE } = require('./theme.js');
27
27
  const { LiveRegion, termWidth, w } = require('./screen.js');
@@ -41,6 +41,30 @@ const { fmtTokens, fmtEur, maskKey } = require('./format.js');
41
41
 
42
42
  const VERSION = require('../package.json').version;
43
43
 
44
+ /**
45
+ * The persona for a turn this host did not get an explicit system prompt for.
46
+ *
47
+ * Built once and cached: it is the stable head of every request, and a prompt
48
+ * that changed between turns would defeat the provider's prompt cache for the
49
+ * whole conversation behind it.
50
+ */
51
+ let _defaultSystem = null;
52
+ function defaultSystemPrompt() {
53
+ if (_defaultSystem === null) {
54
+ try {
55
+ _defaultSystem = buildSystemPrompt({
56
+ platform: process.platform,
57
+ arch: process.arch,
58
+ homedir: os.homedir(),
59
+ cwd: process.cwd(),
60
+ });
61
+ } catch {
62
+ _defaultSystem = ''; // never let persona construction break a turn
63
+ }
64
+ }
65
+ return _defaultSystem || undefined;
66
+ }
67
+
44
68
  function createApp(options = {}) {
45
69
  const opts = {
46
70
  model: null,
@@ -458,7 +482,12 @@ function createApp(options = {}) {
458
482
  prompt,
459
483
  messages: history,
460
484
  model: commandCtx.model || undefined,
461
- system: opts.system,
485
+ // A caller-supplied system wins; otherwise the shared persona. Sent
486
+ // explicitly rather than left to the server, because the pooled
487
+ // route takes the system prompt from the request and the CLI was
488
+ // sending none — the model got tool schemas and no rule about when
489
+ // to use them.
490
+ system: opts.system || defaultSystemPrompt(),
462
491
  maxTokens: opts.maxTokens,
463
492
  // `effort` is the pooled budget control, and it travels on every turn
464
493
  // — this host only ever runs the 'aegis' class, whose calls are sized
package/src/deps.js CHANGED
@@ -59,12 +59,19 @@ const enginePath = resolveShared(path.join('desktop', 'lib', 'local', 'engine.js
59
59
  // Subagent role presets (/agents lists these; the model's task tool delegates
60
60
  // to them by name) — lives beside engine.js, staged into the same vendor dir.
61
61
  const agentsPath = resolveShared(path.join('desktop', 'lib', 'local', 'agents.js'));
62
+ // The persona the tool loop runs under. The CLI sent NO system prompt at all,
63
+ // so a pooled turn reached the model as a bare user message plus tool schemas
64
+ // with nothing saying when a tool is appropriate — which is how "hey" started
65
+ // running shell commands. Same file as the GUI, for the same reason engine.js
66
+ // is: two personas would drift, and only one of them would get fixed.
67
+ const promptPath = resolveShared(path.join('desktop', 'lib', 'local', 'prompt.js'));
62
68
 
63
69
  const { createClient } = require(clientPath);
64
70
  const { createTools } = require(toolsPath);
65
71
  const { usageTokens } = require(usagePath);
66
72
  const { createLocalEngine } = require(enginePath);
67
73
  const { agentRoles, agentRoleLabel } = require(agentsPath);
74
+ const { buildSystemPrompt } = require(promptPath);
68
75
 
69
76
  module.exports = {
70
77
  createClient,
@@ -73,6 +80,7 @@ module.exports = {
73
80
  createLocalEngine,
74
81
  agentRoles,
75
82
  agentRoleLabel,
76
- paths: { client: clientPath, tools: toolsPath, usage: usagePath, engine: enginePath, agents: agentsPath },
83
+ buildSystemPrompt,
84
+ paths: { client: clientPath, tools: toolsPath, usage: usagePath, engine: enginePath, agents: agentsPath, prompt: promptPath },
77
85
  roots,
78
86
  };
@@ -31,8 +31,15 @@ const MAIN_CHAT_PROMPT =
31
31
  `- Use tools silently. A one-line reason is enough; do not narrate your plan as a story. ` +
32
32
  `- Never claim what a tool found or what a command returned before the tool actually runs. ` +
33
33
  ` Report only the results you really received. ` +
34
- `- Act, don't just inspect. After at most 2 rounds of reading or exploration, start making ` +
35
- ` changes with writeFile or editFile. Reconnaissance is not progress implement, then verify.\n` +
34
+ `- Match the response to the request. A greeting, a question about something you already ` +
35
+ ` know, or a request for an opinion is answered in plain words with NO tools. Only reach ` +
36
+ ` for a tool when the answer genuinely depends on something in this repo or on this ` +
37
+ ` machine. "hey" is not a task.\n` +
38
+ `- WHEN THE USER HAS ASKED FOR WORK: act, don't just inspect. After at most 2 rounds of ` +
39
+ ` reading or exploration, start making changes with writeFile or editFile. Reconnaissance ` +
40
+ ` is not progress — implement, then verify. This rule is about HOW to carry out a task you ` +
41
+ ` were given; it is never a reason to invent one. Never write or modify a file the user ` +
42
+ ` did not ask you to touch.\n` +
36
43
  `- When you have what you need, stop using tools and give a concise, direct answer to the ` +
37
44
  ` user's question. Never end your turn with an intention like "Let me check…" or "I'll now…" ` +
38
45
  ` — that is not an answer. ` +
@@ -81,14 +81,12 @@ const fail = (error) => ({ ok: false, error: cap(error) });
81
81
  const SCHEMAS = {
82
82
  readFile: {
83
83
  name: 'readFile',
84
- description:
85
- 'Reads a file from the local filesystem. The file_path parameter must be an absolute path. ' +
86
- 'Returns line-numbered content.',
84
+ description: 'Read a file. Absolute path. Returns line-numbered content.',
87
85
  parameters: {
88
86
  type: 'object',
89
87
  properties: {
90
- file_path: { type: 'string', description: 'The absolute path to the file to read' },
91
- offset: { type: 'number', description: 'The line number to start reading from (0-based)' },
88
+ file_path: { type: 'string', description: 'Absolute path' },
89
+ offset: { type: 'number', description: 'Start line (0-based)' },
92
90
  limit: { type: 'number', description: `The number of lines to read (max 10000, default ${READ_LINE_CAP})` },
93
91
  },
94
92
  required: ['file_path'],
@@ -98,13 +96,12 @@ const SCHEMAS = {
98
96
  writeFile: {
99
97
  name: 'writeFile',
100
98
  description:
101
- 'Writes a file to the local filesystem. Parent directories are created automatically. ' +
102
- 'Use it to create or replace a whole file; it overwrites whatever was there.',
99
+ 'Write a whole file, overwriting it. Parent dirs are created. Use editFile to change part of one.',
103
100
  parameters: {
104
101
  type: 'object',
105
102
  properties: {
106
- file_path: { type: 'string', description: 'The absolute path to the file to write' },
107
- content: { type: 'string', description: 'The full contents of the file' },
103
+ file_path: { type: 'string', description: 'Absolute path' },
104
+ content: { type: 'string', description: 'Full file contents' },
108
105
  },
109
106
  required: ['file_path', 'content'],
110
107
  additionalProperties: false,
@@ -113,16 +110,15 @@ const SCHEMAS = {
113
110
  editFile: {
114
111
  name: 'editFile',
115
112
  description:
116
- 'Performs an exact string replacement in a file. old_string must be unique in the file ' +
117
- 'unless replace_all is true. Use this instead of writeFile when changing part of an ' +
118
- 'existing file — it fails loudly on a non-unique or missing match instead of guessing.',
113
+ 'Exact string replacement. old_string must be unique unless replace_all. Fails loudly on a ' +
114
+ 'missing or ambiguous match rather than guessing.',
119
115
  parameters: {
120
116
  type: 'object',
121
117
  properties: {
122
- file_path: { type: 'string', description: 'The absolute path to the file to modify' },
123
- old_string: { type: 'string', description: 'The text to replace (must be unique unless replace_all is true)' },
124
- new_string: { type: 'string', description: 'The text to replace it with' },
125
- replace_all: { type: 'boolean', description: 'If true, replace all occurrences of old_string' },
118
+ file_path: { type: 'string', description: 'Absolute path' },
119
+ old_string: { type: 'string', description: 'Text to replace; must be unique unless replace_all' },
120
+ new_string: { type: 'string', description: 'Replacement text' },
121
+ replace_all: { type: 'boolean', description: 'Replace every occurrence' },
126
122
  },
127
123
  required: ['file_path', 'old_string', 'new_string'],
128
124
  additionalProperties: false,
@@ -130,9 +126,7 @@ const SCHEMAS = {
130
126
  },
131
127
  listDir: {
132
128
  name: 'listDir',
133
- description:
134
- 'Lists one directory (non-recursive). Directories are marked with a trailing slash. ' +
135
- 'Skips node_modules, .git and dist.',
129
+ description: 'List one directory (non-recursive). Skips node_modules, .git, dist.',
136
130
  parameters: {
137
131
  type: 'object',
138
132
  properties: {
@@ -145,12 +139,12 @@ const SCHEMAS = {
145
139
  glob: {
146
140
  name: 'glob',
147
141
  description:
148
- 'Find files matching a glob pattern. Supports **, * and ?. Skips node_modules, .git and dist by default.',
142
+ 'Find files by glob (**, *, ?). Skips node_modules, .git, dist.',
149
143
  parameters: {
150
144
  type: 'object',
151
145
  properties: {
152
- pattern: { type: 'string', description: 'The glob pattern to match, e.g. "**/*.test.js"' },
153
- path: { type: 'string', description: 'The directory to search from (default: the working directory)' },
146
+ pattern: { type: 'string', description: 'Glob, e.g. "**/*.test.js"' },
147
+ path: { type: 'string', description: 'Search root (default: cwd)' },
154
148
  },
155
149
  required: ['pattern'],
156
150
  additionalProperties: false,
@@ -158,9 +152,7 @@ const SCHEMAS = {
158
152
  },
159
153
  grep: {
160
154
  name: 'grep',
161
- description:
162
- 'Search file contents using a regular expression. Returns file:line matches. ' +
163
- 'Skips node_modules, .git and dist by default.',
155
+ description: 'Search file contents by regex. Returns file:line matches. Skips node_modules, .git, dist.',
164
156
  parameters: {
165
157
  type: 'object',
166
158
  properties: {
@@ -174,15 +166,13 @@ const SCHEMAS = {
174
166
  exec: {
175
167
  name: 'exec',
176
168
  description:
177
- 'Executes a shell command in a persistent shell session and returns its combined ' +
178
- 'stdout+stderr plus the exit code. State (cd, exported env vars) carries across calls ' +
179
- 'within the same turn — it is a real session, not a fresh process each time. ' +
180
- 'Use for system operations, git commands and package management.',
169
+ 'Run a shell command in a PERSISTENT session: cd and exported env carry across calls in ' +
170
+ 'this turn. Returns stdout+stderr and the exit code. Use for git, packages, system ops.',
181
171
  parameters: {
182
172
  type: 'object',
183
173
  properties: {
184
- command: { type: 'string', description: 'The shell command to execute' },
185
- cwd: { type: 'string', description: 'Run this one command in a different directory without moving the session (the session cwd is unchanged for later calls)' },
174
+ command: { type: 'string', description: 'Command to run' },
175
+ cwd: { type: 'string', description: 'Run this one command elsewhere; session cwd unchanged' },
186
176
  timeout: { type: 'number', description: `Timeout in milliseconds (max ${EXEC_TIMEOUT_CAP}, default ${EXEC_TIMEOUT_DEFAULT})` },
187
177
  description: { type: 'string', description: 'A brief description of what the command does (for display)' },
188
178
  },
@@ -193,17 +183,12 @@ const SCHEMAS = {
193
183
  task: {
194
184
  name: 'task',
195
185
  description:
196
- 'Spawn a specialized subagent to autonomously handle a focused, multi-step sub-task. ' +
197
- 'The subagent runs its own tool loop (readFile/writeFile/editFile/listDir/glob/grep/exec) ' +
198
- 'on the same model and returns a final report as the tool result. Use it to delegate work ' +
199
- 'like scanning for vulnerabilities, reviewing code, planning a refactor, or scaffolding a ' +
200
- 'component — give it a complete, self-contained prompt since it cannot ask follow-up ' +
201
- 'questions. Subagents can delegate further with task, so a large job can be split ' +
202
- 'hierarchically as deep as useful.',
186
+ 'Delegate a focused multi-step sub-task to a subagent with its own tool loop; it returns a ' +
187
+ 'final report. Give it a complete, self-contained prompt — it cannot ask follow-ups.',
203
188
  parameters: {
204
189
  type: 'object',
205
190
  properties: {
206
- description: { type: 'string', description: 'A short (3-5 word) description of the sub-task' },
191
+ description: { type: 'string', description: 'Short label (3-5 words)' },
207
192
  subagent_type: {
208
193
  type: 'string',
209
194
  enum: [...agentRoles(), 'general'],