atris 3.30.12 → 3.32.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 (64) hide show
  1. package/AGENTS.md +16 -3
  2. package/FOR_AGENTS.md +81 -0
  3. package/README.md +6 -4
  4. package/atris/CLAUDE.md +8 -0
  5. package/atris/atris.md +51 -19
  6. package/atris/skills/README.md +1 -0
  7. package/atris/skills/blocks/SKILL.md +134 -0
  8. package/atris/skills/clawhub/philosophy-of-work/SKILL.md +56 -0
  9. package/atris/skills/youtube/SKILL.md +31 -11
  10. package/atris.md +15 -0
  11. package/ax +189 -7
  12. package/bin/atris.js +273 -225
  13. package/commands/autoland.js +379 -0
  14. package/commands/autopilot-front.js +273 -0
  15. package/commands/autopilot.js +94 -4
  16. package/commands/business.js +1 -1
  17. package/commands/clean.js +72 -9
  18. package/commands/codex-goal.js +72 -22
  19. package/commands/computer.js +48 -3
  20. package/commands/gm.js +1 -1
  21. package/commands/harvest.js +179 -0
  22. package/commands/init.js +22 -1
  23. package/commands/land.js +442 -0
  24. package/commands/loop-front.js +122 -4
  25. package/commands/member.js +551 -19
  26. package/commands/mission.js +3674 -278
  27. package/commands/play.js +1 -1
  28. package/commands/pulse.js +65 -7
  29. package/commands/run-front.js +144 -0
  30. package/commands/run.js +10 -7
  31. package/commands/sign.js +90 -0
  32. package/commands/strings.js +301 -0
  33. package/commands/task.js +782 -108
  34. package/commands/truth.js +171 -0
  35. package/commands/xp.js +32 -8
  36. package/commands/youtube.js +72 -5
  37. package/decks/README.md +6 -12
  38. package/lib/auto-accept-certified.js +10 -0
  39. package/lib/autoland.js +391 -0
  40. package/lib/context-gatherer.js +0 -8
  41. package/lib/mission-artifact.js +504 -0
  42. package/lib/mission-room.js +846 -0
  43. package/lib/next-moves.js +212 -6
  44. package/lib/operator-next.js +7 -0
  45. package/lib/pulse.js +78 -4
  46. package/lib/runner-command.js +51 -20
  47. package/lib/runs-prune.js +242 -0
  48. package/lib/task-db.js +19 -2
  49. package/lib/task-proof.js +1 -1
  50. package/package.json +4 -4
  51. package/decks/atris-seed-pitch-v3.json +0 -118
  52. package/decks/atris-seed-pitch-v4-skeleton.json +0 -106
  53. package/decks/atris-seed-pitch-v5.json +0 -109
  54. package/decks/atris-seed-pitch-v6.json +0 -137
  55. package/decks/atris-seed-pitch-v7.json +0 -133
  56. package/decks/mark-pincus-narrative.json +0 -102
  57. package/decks/mark-pincus-sourcery.json +0 -94
  58. package/decks/yash-applied-compute-detailed.json +0 -150
  59. package/decks/yash-applied-compute-generalist.json +0 -82
  60. package/decks/yash-applied-compute-narrative.json +0 -54
  61. package/lib/ax-chat-input.js +0 -164
  62. package/lib/ax-goal.js +0 -307
  63. package/lib/ax-prefs.js +0 -63
  64. package/lib/ax-shimmer.js +0 -63
@@ -1,82 +0,0 @@
1
- {
2
- "theme": "ink",
3
- "brand": { "name": "The Generalist", "accent": "." },
4
- "slides": [
5
- {
6
- "type": "title",
7
- "headline": "Own or **be owned.**",
8
- "sub": "Yash Patil, Applied Compute. Custom models on your data. Smaller, cheaper, built for the work you actually do.",
9
- "panel": {
10
- "header": { "title": "Applied Compute", "meta": "loop" },
11
- "rows": [
12
- { "title": "Evals", "sub": "your scorecard", "value": "1", "sev": 2 },
13
- { "title": "Post-train", "sub": "hill climb RLVR", "value": "2", "sev": 0, "active": true },
14
- { "title": "Deploy", "sub": "production at scale", "value": "3", "sev": 1 },
15
- { "title": "Improve", "sub": "continuous learning", "value": "4", "sev": 2 }
16
- ],
17
- "footer": { "left": "open weights in", "right": "your evals stay" }
18
- }
19
- },
20
- {
21
- "type": "quote",
22
- "text": "Evals are the new **PRD.**",
23
- "author": "Yash Patil",
24
- "role": "Applied Compute · The Generalist"
25
- },
26
- {
27
- "type": "versus",
28
- "heading": "Right model for the right task",
29
- "left": {
30
- "label": "Frontier model",
31
- "items": ["sets the floor", "hardest 20% of spend", "shared capability", "blowtorch scale"]
32
- },
33
- "right": {
34
- "label": "Your model",
35
- "items": ["raises the ceiling", "80% of task spend", "private evals", "cost + latency fit"]
36
- }
37
- },
38
- {
39
- "type": "timeline",
40
- "heading": "How Applied Compute works",
41
- "steps": [
42
- { "label": "eval", "sub": "define good" },
43
- { "label": "data", "sub": "your traces" },
44
- { "label": "train", "sub": "RLVR hill climb", "active": true },
45
- { "label": "serve", "sub": "production" },
46
- { "label": "improve", "sub": "never ends" }
47
- ],
48
- "sub": "Start from eval. Train on data that looks like the eval. Deploy. Loop forever."
49
- },
50
- {
51
- "type": "receipt",
52
- "heading": "DoorDash beat frontier on one task.",
53
- "sub": "100k+ merchants per year. Menu photo in. Storefront out.",
54
- "receiptTitle": "case study · merchant onboarding",
55
- "fields": [
56
- { "k": "input", "v": "unstructured menu photos" },
57
- { "k": "eval", "v": "what a good storefront looks like" },
58
- { "k": "train", "v": "small specialized model + RL" },
59
- { "k": "result", "v": "quality above frontier on this task" },
60
- { "k": "why", "v": "task-specific beats general at scale" }
61
- ],
62
- "stamp": "own the intelligence"
63
- },
64
- {
65
- "type": "metricgrid",
66
- "heading": "The squeeze",
67
- "metrics": [
68
- { "value": "$1.3B", "label": "Applied Compute valuation, year one" },
69
- { "value": "100k+", "label": "merchants onboarded at DoorDash yearly" },
70
- { "value": "80/20", "label": "custom vs frontier spend split Yash cites" },
71
- { "value": "Decades", "label": "AI diffusion into the real economy" }
72
- ]
73
- },
74
- {
75
- "type": "hero",
76
- "headline": "Train on your evals. **Keep your moat.**",
77
- "sub": "DoorDash, Cognition, Mercor. Ex-OpenAI post-training. Open weights in, private scorecards stay home.",
78
- "mono": "youtube.com/watch?v=B-MxM-OqON8",
79
- "footer": "generalist.com · appliedcompute.com · 2026"
80
- }
81
- ]
82
- }
@@ -1,54 +0,0 @@
1
- {
2
- "theme": "ink",
3
- "brand": { "name": "The Generalist", "accent": "." },
4
- "slides": [
5
- {
6
- "type": "interstitial",
7
- "kicker": "Applied Compute · The Generalist · 68 min",
8
- "text": "Own or **be owned.**"
9
- },
10
- {
11
- "type": "lede",
12
- "headline": "Frontier models are a **blowtorch.** Most jobs need a knife.",
13
- "dek": "Yash Patil left OpenAI to help companies train custom models on private evals. DoorDash, Cognition, Mercor.",
14
- "byline": "Episode · Own or Be Owned"
15
- },
16
- {
17
- "type": "statement",
18
- "text": "Evals are the new **PRD.**",
19
- "sub": "Every company codifies what good looks like. Those scorecards stay private. They will never ship them to frontier providers."
20
- },
21
- {
22
- "type": "prose",
23
- "heading": "If evals are yours, post-training should be yours too.",
24
- "paragraphs": [
25
- "Open weights in. Your hill to climb. RLVR on traces that look like the eval, not the internet.",
26
- "Goldman will not send its production scorecard to a shared frontier API. Neither will DoorDash, Cognition, or Mercor."
27
- ]
28
- },
29
- {
30
- "type": "bignumber",
31
- "number": "$1.3B",
32
- "label": "Applied Compute valuation, year one",
33
- "sub": "Mercor as customer. Ex-OpenAI post-training team."
34
- },
35
- {
36
- "type": "split",
37
- "heading": "DoorDash beat frontier on one task.",
38
- "body": "100k+ merchants onboarded per year. Menu photo in, storefront out. A small specialized model trained on what a good storefront looks like, not general intelligence.",
39
- "accent": "100k+",
40
- "accentSub": "merchants per year · one narrow task · owned intelligence"
41
- },
42
- {
43
- "type": "statement",
44
- "text": "80% of spend on custom models. **20% on frontier.**",
45
- "sub": "Frontier sets the floor. Your model raises the ceiling. AI diffusion into the real economy takes decades, not quarters."
46
- },
47
- {
48
- "type": "interstitial",
49
- "text": "Train on your evals. **Keep your moat.**",
50
- "sub": "youtube.com/watch?v=B-MxM-OqON8 · generalist.com · appliedcompute.com",
51
- "align": "START"
52
- }
53
- ]
54
- }
@@ -1,164 +0,0 @@
1
- const MULTILINE_CSI = [
2
- '\x1b[13;2~',
3
- '\x1b[27;2;13~',
4
- '\x1b[23~',
5
- '\x1b[13;2u',
6
- ];
7
-
8
- const MULTILINE_CSI_RE = /\x1b\[(?:13;2~|27;2;13~|23~|13;2u)/;
9
- const MULTILINE_CSI_VISIBLE_RE = /\[(?:13;2~|27;2;13~|23~|13;2u)/g;
10
-
11
- // Node's readline cannot emit a 'line' whose buffer contains a literal "\n"
12
- // (it reorders the text around the newline on submit). So newlines live in the
13
- // buffer as this sentinel while typing is rendered, then are restored to "\n"
14
- // when the answer is read back via stripMultilineCsiText.
15
- const MULTILINE_PLACEHOLDER = '\x1f';
16
- const MULTILINE_PLACEHOLDER_RE = /\x1f/g;
17
-
18
- function isMultilineCsiPrefix(buffer) {
19
- const value = String(buffer || '');
20
- if (!value) return false;
21
- return MULTILINE_CSI.some((seq) => seq.startsWith(value));
22
- }
23
-
24
- function isMultilineCsiComplete(buffer) {
25
- return MULTILINE_CSI.includes(String(buffer || ''));
26
- }
27
-
28
- function isMultilineCsiKey(key) {
29
- const sequence = String(key?.sequence || '');
30
- return MULTILINE_CSI_RE.test(sequence);
31
- }
32
-
33
- function isMultilineInsertKey(str, key) {
34
- if (!key) return str === '\n';
35
- if (key.shift && (key.name === 'return' || key.name === 'enter')) return true;
36
- if (key.meta && (key.name === 'return' || key.name === 'enter')) return true;
37
- if (str === '\n' && key.name !== 'return') return true;
38
- return false;
39
- }
40
-
41
- function isSubmitKey(str, key) {
42
- if (key && (key.name === 'return' || key.name === 'enter') && !key.shift && !key.meta) return true;
43
- if (!key && str === '\r') return true;
44
- return false;
45
- }
46
-
47
- function stripMultilineCsiText(text) {
48
- return String(text || '')
49
- .replace(MULTILINE_PLACEHOLDER_RE, '\n')
50
- .replace(/\x1b\[20[01]~/g, '')
51
- .replace(/\x1b\[(?:13;2~|27;2;13~|23~|13;2u)/g, '\n')
52
- .replace(MULTILINE_CSI_VISIBLE_RE, '\n')
53
- .replace(/\^?\[\[27;2;13~/g, '\n');
54
- }
55
-
56
- // The raw bytes a keypress represents. readline's keypress decoder mangles
57
- // some CSI sequences (e.g. it splits \x1b[27;2;13~ into a malformed escape
58
- // whose `sequence` is \x1b[27;2; followed by literal "1", "3", "~"), so when
59
- // `str` is empty we fall back to the decoded `sequence`.
60
- function keyBytes(str, key) {
61
- if (typeof str === 'string' && str.length > 0) return str;
62
- if (key && typeof key.sequence === 'string' && key.sequence.length > 0) return key.sequence;
63
- return typeof str === 'string' ? str : '';
64
- }
65
-
66
- function defaultInsertBreak(rl) {
67
- if (!rl) return;
68
- // _insertString splices a literal newline at the cursor; rl.write('\n')
69
- // would instead SUBMIT the line, so it must not be used here.
70
- if (typeof rl._insertString === 'function') {
71
- rl._insertString('\n');
72
- return;
73
- }
74
- if (typeof rl.line === 'string') {
75
- const cursor = Number.isFinite(rl.cursor) ? rl.cursor : rl.line.length;
76
- rl.line = rl.line.slice(0, cursor) + '\n' + rl.line.slice(cursor);
77
- rl.cursor = cursor + 1;
78
- if (typeof rl._refreshLine === 'function') rl._refreshLine();
79
- return;
80
- }
81
- if (typeof rl.write === 'function') rl.write('\n');
82
- }
83
-
84
- function attachMultilineChatInput(rl, { insertBreak = defaultInsertBreak, onSubmit } = {}) {
85
- if (!rl || typeof rl._ttyWrite !== 'function') return () => {};
86
- const ttyWrite = rl._ttyWrite.bind(rl);
87
- let escBuffer = null;
88
-
89
- rl._ttyWrite = (str, key) => {
90
- const bytes = keyBytes(str, key);
91
-
92
- // Mid-sequence: keypress decoder shredded a multiline CSI across calls and
93
- // we're reassembling it (e.g. \x1b[27;2; then "1", "3", "~").
94
- if (escBuffer !== null) {
95
- escBuffer += bytes;
96
- if (isMultilineCsiComplete(escBuffer)) {
97
- escBuffer = null;
98
- insertBreak(rl);
99
- return;
100
- }
101
- if (isMultilineCsiPrefix(escBuffer) && escBuffer.length <= 24) {
102
- return;
103
- }
104
- // Diverged from every target sequence: replay the swallowed bytes
105
- // literally (current keypress is already included in the buffer).
106
- const replay = escBuffer;
107
- escBuffer = null;
108
- for (const ch of replay) ttyWrite(ch);
109
- return;
110
- }
111
-
112
- // Whole multiline CSI delivered in a single keypress (terminals or Node
113
- // versions that don't shred it).
114
- if (isMultilineCsiKey(key) || MULTILINE_CSI_RE.test(bytes)) {
115
- insertBreak(rl);
116
- return;
117
- }
118
-
119
- // Start of a shredded multiline CSI: begin buffering from the raw escape.
120
- if (bytes && bytes.charCodeAt(0) === 0x1b && isMultilineCsiPrefix(bytes)) {
121
- escBuffer = bytes;
122
- if (isMultilineCsiComplete(escBuffer)) {
123
- escBuffer = null;
124
- insertBreak(rl);
125
- }
126
- return;
127
- }
128
-
129
- // Plain Shift+Enter / Meta+Enter / synthetic \n.
130
- if (isMultilineInsertKey(str, key)) {
131
- insertBreak(rl);
132
- return;
133
- }
134
-
135
- // Submit (Enter): the line buffer keeps its literal "\n" so readline's own
136
- // multiline cursor math advances past every wrapped row (otherwise the box
137
- // bottom border overwrites a row). readline mangles the emitted 'line'
138
- // string when it holds a newline, so we hand the true buffer to onSubmit
139
- // and the caller reads that instead of the line event.
140
- if (isSubmitKey(str, key) && typeof rl.line === 'string' && rl.line.includes('\n')) {
141
- if (typeof onSubmit === 'function') onSubmit(rl.line);
142
- }
143
-
144
- ttyWrite(str, key);
145
- };
146
-
147
- return () => {
148
- rl._ttyWrite = ttyWrite;
149
- escBuffer = null;
150
- };
151
- }
152
-
153
- module.exports = {
154
- MULTILINE_CSI,
155
- MULTILINE_PLACEHOLDER,
156
- attachMultilineChatInput,
157
- isMultilineCsiComplete,
158
- isMultilineCsiKey,
159
- isMultilineInsertKey,
160
- isMultilineCsiPrefix,
161
- isSubmitKey,
162
- stripMultilineCsiText,
163
- keyBytes,
164
- };
package/lib/ax-goal.js DELETED
@@ -1,307 +0,0 @@
1
- const GOAL_CLEAR_ALIASES = new Set(['clear', 'stop', 'off', 'reset', 'none', 'cancel']);
2
- const GOAL_ACHIEVED_RE = /^\s*GOAL_ACHIEVED:\s*(.+)$/im;
3
- const GOAL_JSON_RE = /\{[\s\S]*"achieved"\s*:\s*(true|false)[\s\S]*\}/i;
4
-
5
- function parseTokenBudget(raw) {
6
- const text = String(raw || '').trim().toUpperCase();
7
- const match = text.match(/^(\d+(?:\.\d+)?)([KMB])?$/);
8
- if (!match) return null;
9
- let value = Number(match[1]);
10
- if (match[2] === 'K') value *= 1000;
11
- if (match[2] === 'M') value *= 1000000;
12
- if (match[2] === 'B') value *= 1000000000;
13
- return Math.round(value);
14
- }
15
-
16
- function parseGoalCommand(line) {
17
- const raw = String(line || '').trim();
18
- const lower = raw.toLowerCase();
19
- if (!lower.startsWith('/goal')) return null;
20
-
21
- const rest = raw.slice(5).trim();
22
- if (!rest) return { action: 'status' };
23
-
24
- const firstWord = rest.split(/\s+/)[0].toLowerCase();
25
- if (GOAL_CLEAR_ALIASES.has(firstWord)) return { action: 'clear' };
26
-
27
- let condition = rest;
28
- let tokenBudget = null;
29
- const tokensMatch = condition.match(/^--tokens\s+(\S+)\s+([\s\S]+)$/i);
30
- if (tokensMatch) {
31
- tokenBudget = parseTokenBudget(tokensMatch[1]);
32
- condition = tokensMatch[2].trim();
33
- }
34
-
35
- const maxTurnsMatch = condition.match(/\b(?:max|stop after|within)\s+(\d+)\s+turns?\b/i);
36
- const maxTurns = maxTurnsMatch ? Number(maxTurnsMatch[1]) : null;
37
-
38
- if (!condition) return { action: 'status' };
39
- return { action: 'set', condition, maxTurns, tokenBudget };
40
- }
41
-
42
- function createGoalState(condition, options = {}) {
43
- return {
44
- active: true,
45
- condition: String(condition || '').trim(),
46
- maxTurns: Number.isFinite(options.maxTurns) ? options.maxTurns : null,
47
- tokenBudget: Number.isFinite(options.tokenBudget) ? options.tokenBudget : null,
48
- turns: 0,
49
- evalTurns: 0,
50
- tokensUsed: 0,
51
- creditsUsed: 0,
52
- startedAt: Date.now(),
53
- lastReason: 'Goal started — working toward the condition.',
54
- achieved: false,
55
- achievedAt: null,
56
- };
57
- }
58
-
59
- function goalElapsedMs(goal) {
60
- const end = goal.achievedAt || Date.now();
61
- return Math.max(0, end - (goal.startedAt || end));
62
- }
63
-
64
- function truncateGoalText(text, limit = 72) {
65
- const value = String(text || '').trim();
66
- if (value.length <= limit) return value;
67
- return `${value.slice(0, limit - 1)}…`;
68
- }
69
-
70
- function compactGoalHistory(history = [], limit = 8) {
71
- return history
72
- .slice(-limit)
73
- .map(turn => `${turn.role}: ${String(turn.content || '').slice(0, 900)}`)
74
- .join('\n');
75
- }
76
-
77
- function buildGoalDirective(goal, options = {}) {
78
- const condition = goal.condition;
79
- const reason = goal.lastReason && goal.turns > 0
80
- ? `\nEvaluator guidance from last turn: ${goal.lastReason}`
81
- : '';
82
- return [
83
- 'Work autonomously toward this completion condition:',
84
- condition,
85
- '',
86
- 'Use local tools as needed. Do not ask the user for permission between steps.',
87
- 'Do not declare the goal achieved yourself — a separate evaluator decides that.',
88
- reason,
89
- options.continue ? '\nContinue from the recent conversation below.' : '',
90
- ].filter(Boolean).join('\n');
91
- }
92
-
93
- function buildGoalEvalPrompt(goal, history, lastOutput) {
94
- return [
95
- 'You are a strict goal evaluator for a coding agent session.',
96
- 'Reply with JSON only: {"achieved": true|false, "reason": "short reason"}',
97
- 'Judge only from observable evidence in the transcript and latest output.',
98
- 'If the condition requires command output or file contents, require proof in the transcript.',
99
- '',
100
- `Goal condition: ${goal.condition}`,
101
- '',
102
- 'Recent conversation:',
103
- compactGoalHistory(history),
104
- '',
105
- 'Latest assistant output:',
106
- String(lastOutput || '').slice(0, 4000),
107
- ].join('\n');
108
- }
109
-
110
- function parseGoalEvalResponse(text) {
111
- const raw = String(text || '').trim();
112
- if (!raw) return null;
113
-
114
- const marker = raw.match(GOAL_ACHIEVED_RE);
115
- if (marker) {
116
- return { achieved: true, reason: marker[1].trim() };
117
- }
118
-
119
- const jsonMatch = raw.match(GOAL_JSON_RE);
120
- if (!jsonMatch) return null;
121
- try {
122
- const parsed = JSON.parse(jsonMatch[0]);
123
- return {
124
- achieved: parsed.achieved === true,
125
- reason: String(parsed.reason || '').trim() || (parsed.achieved ? 'Condition met.' : 'Condition not met yet.'),
126
- };
127
- } catch {
128
- return null;
129
- }
130
- }
131
-
132
- function parseGoalAchievedMarker(text) {
133
- const marker = String(text || '').match(GOAL_ACHIEVED_RE);
134
- if (!marker) return null;
135
- return { achieved: true, reason: marker[1].trim() };
136
- }
137
-
138
- function accumulateGoalUsage(goal, result, creditsFromState) {
139
- if (!goal || !result) return;
140
- const credits = typeof creditsFromState === 'function' ? creditsFromState(result) : null;
141
- if (Number.isFinite(credits) && credits > 0) {
142
- goal.creditsUsed += credits;
143
- }
144
- const approxTokens = Math.max(0, Math.round(String(result.output || '').length / 4));
145
- goal.tokensUsed += approxTokens;
146
- }
147
-
148
- function goalBudgetExceeded(goal) {
149
- if (!goal || !Number.isFinite(goal.tokenBudget)) return false;
150
- return goal.tokensUsed >= goal.tokenBudget;
151
- }
152
-
153
- function goalTurnLimitReached(goal) {
154
- if (!goal || !Number.isFinite(goal.maxTurns)) return false;
155
- return goal.turns >= goal.maxTurns;
156
- }
157
-
158
- function clearGoalState(goal) {
159
- if (!goal) return null;
160
- goal.active = false;
161
- return goal;
162
- }
163
-
164
- function finishGoalAchieved(goal, reason) {
165
- goal.active = false;
166
- goal.achieved = true;
167
- goal.achievedAt = Date.now();
168
- goal.lastReason = reason || 'Condition met.';
169
- return goal;
170
- }
171
-
172
- function formatGoalCounter(goal) {
173
- const turns = `${goal.turns}${Number.isFinite(goal.maxTurns) ? `/${goal.maxTurns}` : ''}`;
174
- const duration = `${Math.max(1, Math.round(goalElapsedMs(goal) / 1000))}s`;
175
- const usage = [];
176
- if (goal.creditsUsed > 0) usage.push(`${goal.creditsUsed} credits`);
177
- if (goal.tokensUsed > 0) usage.push(`~${goal.tokensUsed} tokens`);
178
- return { turns, duration, usage: usage.join(' · ') };
179
- }
180
-
181
- function formatGoalStatus(goal, options = {}) {
182
- const paint = options.paint || ((text) => String(text));
183
- if (!goal) {
184
- return 'No active goal. Set one with /goal <condition>.';
185
- }
186
-
187
- const counter = formatGoalCounter(goal);
188
- const lines = [];
189
-
190
- if (goal.active) {
191
- lines.push(paint('◎ /goal active', [options.bold, options.magenta]));
192
- lines.push(paint(goal.condition, [options.bold]));
193
- lines.push(paint(`turn ${counter.turns} · ${counter.duration}${counter.usage ? ` · ${counter.usage}` : ''}`, [options.muted]));
194
- if (goal.lastReason) lines.push(paint(`reason: ${goal.lastReason}`, [options.muted]));
195
- if (Number.isFinite(goal.tokenBudget)) {
196
- lines.push(paint(`budget: ~${goal.tokensUsed}/${goal.tokenBudget} tokens`, [options.muted]));
197
- }
198
- return lines.join('\n');
199
- }
200
-
201
- if (goal.achieved) {
202
- lines.push(paint('✦ /goal achieved', [options.bold, options.ok || options.magenta]));
203
- lines.push(paint(goal.condition, [options.bold]));
204
- lines.push(paint(`${counter.turns} turns · ${counter.duration}${counter.usage ? ` · ${counter.usage}` : ''}`, [options.muted]));
205
- if (goal.lastReason) lines.push(paint(`reason: ${goal.lastReason}`, [options.muted]));
206
- return lines.join('\n');
207
- }
208
-
209
- lines.push(paint('Goal stopped.', [options.muted]));
210
- lines.push(paint(goal.condition, [options.bold]));
211
- lines.push(paint(`turn ${counter.turns} · ${counter.duration}`, [options.muted]));
212
- if (goal.lastReason) lines.push(paint(`reason: ${goal.lastReason}`, [options.muted]));
213
- return lines.join('\n');
214
- }
215
-
216
- function formatGoalActiveBanner(goal, options = {}) {
217
- if (!goal || !goal.active) return '';
218
- const paint = options.paint || ((text) => String(text));
219
- const counter = formatGoalCounter(goal);
220
- return paint(
221
- `◎ /goal active · ${truncateGoalText(goal.condition, 56)} · turn ${counter.turns}${Number.isFinite(goal.maxTurns) ? `/${goal.maxTurns}` : ''} · ${counter.duration}`,
222
- [options.bold, options.magenta]
223
- );
224
- }
225
-
226
- function formatGoalAchieved(goal, options = {}) {
227
- const paint = options.paint || ((text) => String(text));
228
- const counter = formatGoalCounter(goal);
229
- return [
230
- paint('✦ Goal achieved', [options.bold, options.magenta]),
231
- paint(goal.condition, [options.bold, options.accent]),
232
- paint(`${counter.turns} turns · ${counter.duration}${counter.usage ? ` · ${counter.usage}` : ''}`, [options.muted]),
233
- goal.lastReason ? paint(`reason: ${goal.lastReason}`, [options.muted]) : '',
234
- ].filter(Boolean).join('\n');
235
- }
236
-
237
- function formatGoalContinue(goal, options = {}) {
238
- const paint = options.paint || ((text) => String(text));
239
- return paint(`◎ continuing goal · turn ${goal.turns + 1}${Number.isFinite(goal.maxTurns) ? `/${goal.maxTurns}` : ''} · ${goal.lastReason}`, [options.magenta]);
240
- }
241
-
242
- function formatGoalStopped(goal, reason, options = {}) {
243
- const paint = options.paint || ((text) => String(text));
244
- const counter = formatGoalCounter(goal);
245
- return [
246
- paint('◎ Goal stopped', [options.bold, options.muted]),
247
- paint(reason, [options.muted]),
248
- paint(`${counter.turns} turns · ${counter.duration}`, [options.muted]),
249
- ].join('\n');
250
- }
251
-
252
- async function evaluateGoalTurn(goal, ctx = {}, deps = {}) {
253
- const marker = parseGoalAchievedMarker(ctx.lastOutput);
254
- if (marker) return marker;
255
-
256
- if (typeof deps.evaluateGoal === 'function') {
257
- return deps.evaluateGoal(goal, ctx);
258
- }
259
-
260
- if (typeof deps.postTurn !== 'function') {
261
- return { achieved: false, reason: goal.lastReason || 'Condition not met yet.' };
262
- }
263
-
264
- const evalOutput = { isTTY: false, write() {} };
265
- try {
266
- const result = await deps.postTurn(buildGoalEvalPrompt(goal, ctx.history || [], ctx.lastOutput), {
267
- ...(ctx.turnOptions || {}),
268
- mode: 'fast',
269
- history: [],
270
- output: evalOutput,
271
- showProgress: false,
272
- goalEval: true,
273
- });
274
- const parsed = parseGoalEvalResponse(result.output);
275
- if (parsed) return parsed;
276
- } catch (error) {
277
- return { achieved: false, reason: `Evaluator unavailable: ${error.message}` };
278
- }
279
-
280
- return { achieved: false, reason: goal.lastReason || 'Condition not met yet.' };
281
- }
282
-
283
- module.exports = {
284
- GOAL_CLEAR_ALIASES,
285
- accumulateGoalUsage,
286
- buildGoalDirective,
287
- buildGoalEvalPrompt,
288
- clearGoalState,
289
- compactGoalHistory,
290
- createGoalState,
291
- evaluateGoalTurn,
292
- finishGoalAchieved,
293
- formatGoalAchieved,
294
- formatGoalActiveBanner,
295
- formatGoalContinue,
296
- formatGoalCounter,
297
- formatGoalStatus,
298
- formatGoalStopped,
299
- goalBudgetExceeded,
300
- goalElapsedMs,
301
- goalTurnLimitReached,
302
- parseGoalAchievedMarker,
303
- parseGoalCommand,
304
- parseGoalEvalResponse,
305
- parseTokenBudget,
306
- truncateGoalText,
307
- };
package/lib/ax-prefs.js DELETED
@@ -1,63 +0,0 @@
1
- const fs = require('fs');
2
- const os = require('os');
3
- const path = require('path');
4
-
5
- const MEMBER_SLUG = 'ax';
6
- const PREFS_PATH = path.join(os.homedir(), '.atris', 'ax.json');
7
- const TRUTHY = new Set(['1', 'true', 'yes', 'on']);
8
-
9
- function truthy(value) {
10
- return TRUTHY.has(String(value || '').trim().toLowerCase());
11
- }
12
-
13
- function loadAxPrefs() {
14
- try {
15
- if (!fs.existsSync(PREFS_PATH)) {
16
- return { member_slug: MEMBER_SLUG, bypass_permissions: false };
17
- }
18
- const parsed = JSON.parse(fs.readFileSync(PREFS_PATH, 'utf8'));
19
- return {
20
- member_slug: MEMBER_SLUG,
21
- bypass_permissions: parsed.bypass_permissions === true,
22
- };
23
- } catch {
24
- return { member_slug: MEMBER_SLUG, bypass_permissions: false };
25
- }
26
- }
27
-
28
- function saveAxPrefs(prefs) {
29
- fs.mkdirSync(path.dirname(PREFS_PATH), { recursive: true });
30
- fs.writeFileSync(PREFS_PATH, `${JSON.stringify({
31
- member_slug: MEMBER_SLUG,
32
- bypass_permissions: prefs.bypass_permissions === true,
33
- updated_at: new Date().toISOString(),
34
- }, null, 2)}\n`);
35
- return loadAxPrefs();
36
- }
37
-
38
- function setBypassPermissions(enabled, { persist = true } = {}) {
39
- const next = { member_slug: MEMBER_SLUG, bypass_permissions: enabled === true };
40
- return persist ? saveAxPrefs(next) : next;
41
- }
42
-
43
- function resolveBypassPermissions(options = {}) {
44
- if (options.bypassPermissions === true) return true;
45
- if (options.bypassPermissions === false) return false;
46
- if (truthy(process.env.AX_BYPASS_PERMISSIONS)) return true;
47
- if (truthy(process.env.AX_SAFE_PERMISSIONS)) return false;
48
- return loadAxPrefs().bypass_permissions === true;
49
- }
50
-
51
- function permissionsLabel(options = {}) {
52
- return resolveBypassPermissions(options) ? 'bypass' : 'safe';
53
- }
54
-
55
- module.exports = {
56
- MEMBER_SLUG,
57
- PREFS_PATH,
58
- loadAxPrefs,
59
- saveAxPrefs,
60
- setBypassPermissions,
61
- resolveBypassPermissions,
62
- permissionsLabel,
63
- };