@amodalai/amodal 0.3.49 → 0.3.50

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@amodalai/amodal",
3
- "version": "0.3.49",
3
+ "version": "0.3.50",
4
4
  "description": "Amodal CLI",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -18,6 +18,7 @@
18
18
  "amodal": "dist/src/main.js"
19
19
  },
20
20
  "dependencies": {
21
+ "@aws-sdk/client-s3": "^3.700.0",
21
22
  "ink": "^6.8.0",
22
23
  "ink-spinner": "^5.0.0",
23
24
  "ink-text-input": "^6.0.0",
@@ -26,12 +27,12 @@
26
27
  "react": "^19.2.4",
27
28
  "yargs": "^17.7.2",
28
29
  "zod": "^4.3.6",
29
- "@amodalai/types": "0.3.49",
30
- "@amodalai/core": "0.3.49",
31
- "@amodalai/db": "0.3.49",
32
- "@amodalai/runtime": "0.3.49",
33
- "@amodalai/studio": "0.3.49",
34
- "@amodalai/runtime-app": "0.3.49"
30
+ "@amodalai/types": "0.3.50",
31
+ "@amodalai/core": "0.3.50",
32
+ "@amodalai/db": "0.3.50",
33
+ "@amodalai/runtime": "0.3.50",
34
+ "@amodalai/studio": "0.3.50",
35
+ "@amodalai/runtime-app": "0.3.50"
35
36
  },
36
37
  "devDependencies": {
37
38
  "@types/node": "^20.11.24",
@@ -0,0 +1,210 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Amodal Labs, Inc.
4
+ * SPDX-License-Identifier: MIT
5
+ */
6
+
7
+ /**
8
+ * Phase 4 — protect dev-mode intent telemetry visibility.
9
+ *
10
+ * `passesQuietFilter` decides which subprocess stderr lines bubble up
11
+ * to the user's terminal during `amodal dev`. The admin subprocess
12
+ * runs with quiet=true (otherwise its INFO stream would drown out the
13
+ * user's own log output), so this filter is the ONLY way Phase 4
14
+ * telemetry events reach the user.
15
+ *
16
+ * The fixtures below are real log lines emitted by the runtime's
17
+ * `formatText` formatter — pinning them ensures a format change in
18
+ * @amodalai/core (e.g. switching to a new `[I]` short-prefix or
19
+ * dropping the event-name prefix) trips this test before it ships and
20
+ * silently breaks dev observability.
21
+ */
22
+
23
+ import {describe, it, expect} from 'vitest';
24
+ import {passesQuietFilter, formatLineForDev} from './dev.js';
25
+
26
+ describe('passesQuietFilter — text-format log lines', () => {
27
+ it('passes warnings and errors', () => {
28
+ expect(passesQuietFilter('[WARN] something_bad {"foo":"bar"}')).toBe(true);
29
+ expect(passesQuietFilter('[ERROR] db_connection_failed {"err":"timeout"}')).toBe(true);
30
+ expect(passesQuietFilter('Error: ECONNREFUSED at ...')).toBe(true);
31
+ });
32
+
33
+ it('passes intent telemetry events (Phase 4)', () => {
34
+ expect(
35
+ passesQuietFilter(
36
+ '[INFO] intent_matched {"intentId":"install-template","sessionId":"abc"}',
37
+ ),
38
+ ).toBe(true);
39
+ expect(
40
+ passesQuietFilter(
41
+ '[INFO] intent_completed {"intentId":"install-template","sessionId":"abc","toolCount":4,"hasText":false,"durationMs":523}',
42
+ ),
43
+ ).toBe(true);
44
+ expect(
45
+ passesQuietFilter(
46
+ '[INFO] intent_fell_through {"intentId":"looks-right","sessionId":"abc","durationMs":12}',
47
+ ),
48
+ ).toBe(true);
49
+ expect(
50
+ passesQuietFilter(
51
+ '[WARN] intent_errored {"intentId":"crash","sessionId":"abc","error":"boom"}',
52
+ ),
53
+ ).toBe(true);
54
+ expect(
55
+ passesQuietFilter(
56
+ '[WARN] intent_blocked_by_confirmation {"intentId":"x","sessionId":"abc","toolName":"y"}',
57
+ ),
58
+ ).toBe(true);
59
+ });
60
+
61
+ it('passes agent_loop_start so intent-vs-LLM ratio is countable', () => {
62
+ expect(
63
+ passesQuietFilter(
64
+ '[INFO] agent_loop_start {"session":"abc","maxTurns":50,"messageCount":1}',
65
+ ),
66
+ ).toBe(true);
67
+ });
68
+
69
+ it('passes route_intent / route_llm markers (one per turn)', () => {
70
+ expect(
71
+ passesQuietFilter('[INFO] route_intent {"sessionId":"abc","intentId":"install-template"}'),
72
+ ).toBe(true);
73
+ expect(
74
+ passesQuietFilter('[INFO] route_llm {"sessionId":"abc","reason":"no_intent_match"}'),
75
+ ).toBe(true);
76
+ expect(
77
+ passesQuietFilter('[INFO] route_llm {"sessionId":"abc","reason":"intent_fell_through","intentId":"looks-right"}'),
78
+ ).toBe(true);
79
+ });
80
+
81
+ it('filters out everything else (most INFO chatter)', () => {
82
+ expect(passesQuietFilter('[INFO] session_created {"session":"abc"}')).toBe(false);
83
+ expect(passesQuietFilter('[INFO] mcp_initialized {"servers":3}')).toBe(false);
84
+ expect(passesQuietFilter('[DEBUG] tool_executing {"tool":"foo"}')).toBe(false);
85
+ expect(passesQuietFilter('[INFO] agent_loop_done {"reason":"model_stop"}')).toBe(false);
86
+ expect(passesQuietFilter(' plain stdout from a child process')).toBe(false);
87
+ expect(passesQuietFilter('')).toBe(false);
88
+ });
89
+ });
90
+
91
+ describe('passesQuietFilter — JSON-format log lines (LOG_FORMAT=json)', () => {
92
+ it('passes intent telemetry events', () => {
93
+ expect(
94
+ passesQuietFilter(
95
+ '{"level":"info","ts":"2026-05-03T18:00:00Z","event":"intent_matched","intentId":"install-template","sessionId":"abc"}',
96
+ ),
97
+ ).toBe(true);
98
+ expect(
99
+ passesQuietFilter(
100
+ '{"level":"info","ts":"2026-05-03T18:00:00Z","event":"intent_completed","intentId":"install-template"}',
101
+ ),
102
+ ).toBe(true);
103
+ });
104
+
105
+ it('passes agent_loop_start', () => {
106
+ expect(
107
+ passesQuietFilter(
108
+ '{"level":"info","ts":"2026-05-03T18:00:00Z","event":"agent_loop_start","session":"abc"}',
109
+ ),
110
+ ).toBe(true);
111
+ });
112
+
113
+ it('filters out non-telemetry JSON lines', () => {
114
+ expect(
115
+ passesQuietFilter(
116
+ '{"level":"info","ts":"2026-05-03T18:00:00Z","event":"session_created","sessionId":"abc"}',
117
+ ),
118
+ ).toBe(false);
119
+ });
120
+ });
121
+
122
+ describe('formatLineForDev — pretty-printing routing telemetry', () => {
123
+ it('reformats route_intent as INTENT one-liner', () => {
124
+ const line = '[INFO] route_intent {"sessionId":"abc","intentId":"install-template","userMessagePreview":"Set up template \'marketing-digest\'."}';
125
+ expect(formatLineForDev(line)).toBe(
126
+ '→ INTENT install-template "Set up template \'marketing-digest\'."',
127
+ );
128
+ });
129
+
130
+ it('reformats route_llm with reason + detail', () => {
131
+ expect(
132
+ formatLineForDev(
133
+ '[INFO] route_llm {"sessionId":"abc","reason":"intent_fell_through","intentId":"looks-right"}',
134
+ ),
135
+ ).toBe('→ LLM intent_fell_through "looks-right"');
136
+
137
+ expect(
138
+ formatLineForDev(
139
+ '[INFO] route_llm {"sessionId":"abc","reason":"no_intent_match","userMessagePreview":"hello there"}',
140
+ ),
141
+ ).toBe('→ LLM no_intent_match "hello there"');
142
+ });
143
+
144
+ it('reformats intent_completed with tool count + duration', () => {
145
+ expect(
146
+ formatLineForDev(
147
+ '[INFO] intent_completed {"intentId":"install-template","sessionId":"abc","toolCount":5,"hasText":false,"durationMs":10368}',
148
+ ),
149
+ ).toBe(' ✓ install-template done (5 tools, 10368ms)');
150
+ });
151
+
152
+ it('reformats intent_fell_through with duration', () => {
153
+ expect(
154
+ formatLineForDev(
155
+ '[INFO] intent_fell_through {"intentId":"looks-right","sessionId":"abc","durationMs":12}',
156
+ ),
157
+ ).toBe(' ↓ looks-right fell through to LLM (12ms)');
158
+ });
159
+
160
+ it('reformats intent_errored with the error message', () => {
161
+ expect(
162
+ formatLineForDev(
163
+ '[WARN] intent_errored {"intentId":"crash","sessionId":"abc","error":"boom","toolCallsStarted":0,"durationMs":3}',
164
+ ),
165
+ ).toBe(' ✗ crash ERRORED: boom');
166
+ });
167
+
168
+ it('suppresses intent_matched (redundant with route_intent)', () => {
169
+ expect(
170
+ formatLineForDev(
171
+ '[INFO] intent_matched {"intentId":"install-template","sessionId":"abc"}',
172
+ ),
173
+ ).toBeNull();
174
+ });
175
+
176
+ it('suppresses agent_loop_start (route_llm already covers it)', () => {
177
+ expect(
178
+ formatLineForDev('[INFO] agent_loop_start {"session":"abc","maxTurns":50}'),
179
+ ).toBeNull();
180
+ });
181
+
182
+ it('reformats tool_log as a nested bullet', () => {
183
+ expect(
184
+ formatLineForDev(
185
+ '[INFO] tool_log {"callId":"intent_0f826d83","message":"Cloned whodatdev/template-marketing-operations-hub into agent repo (13 connection packages installed)","session":"pending"}',
186
+ ),
187
+ ).toBe(
188
+ ' · Cloned whodatdev/template-marketing-operations-hub into agent repo (13 connection packages installed)',
189
+ );
190
+ });
191
+
192
+ it('drops tool_log lines with no message', () => {
193
+ expect(
194
+ formatLineForDev('[INFO] tool_log {"callId":"intent_x","session":"abc"}'),
195
+ ).toBeNull();
196
+ });
197
+
198
+ it('passes through non-routing lines unchanged', () => {
199
+ expect(formatLineForDev('[ERROR] db_connection_failed {"err":"timeout"}')).toBe(
200
+ '[ERROR] db_connection_failed {"err":"timeout"}',
201
+ );
202
+ expect(formatLineForDev('plain stdout from a child')).toBe('plain stdout from a child');
203
+ });
204
+
205
+ it('falls back to raw line on malformed JSON', () => {
206
+ const line = '[INFO] route_intent {not valid json';
207
+ expect(formatLineForDev(line)).toBe(line);
208
+ });
209
+ });
210
+