@flowcodex/core 0.3.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 (97) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -0
  3. package/dist/index-LbxYtxxS.d.ts +560 -0
  4. package/dist/index.d.ts +995 -0
  5. package/dist/index.js +3840 -0
  6. package/dist/index.js.map +1 -0
  7. package/dist/kernel/index.d.ts +1 -0
  8. package/dist/kernel/index.js +551 -0
  9. package/dist/kernel/index.js.map +1 -0
  10. package/package.json +39 -0
  11. package/src/agent/agent-loop.ts +254 -0
  12. package/src/agent/context.ts +99 -0
  13. package/src/agent/conversation-state.ts +44 -0
  14. package/src/agent/provider-runner.ts +241 -0
  15. package/src/agent/system-prompt-builder.ts +193 -0
  16. package/src/execution/compactor.ts +256 -0
  17. package/src/execution/index.ts +7 -0
  18. package/src/execution/output-serializer.ts +90 -0
  19. package/src/execution/schema-validator.ts +124 -0
  20. package/src/execution/tool-executor.ts +276 -0
  21. package/src/execution/tool-registry.ts +104 -0
  22. package/src/index.ts +215 -0
  23. package/src/infrastructure/catalog-parser.ts +218 -0
  24. package/src/infrastructure/index.ts +16 -0
  25. package/src/infrastructure/path-resolver.ts +123 -0
  26. package/src/infrastructure/provider-factory.ts +116 -0
  27. package/src/infrastructure/provider-presets.ts +19 -0
  28. package/src/infrastructure/retry-policy.ts +50 -0
  29. package/src/infrastructure/secret-scrubber.ts +67 -0
  30. package/src/infrastructure/token-counter.ts +156 -0
  31. package/src/infrastructure/tracer.ts +23 -0
  32. package/src/kernel/container.ts +166 -0
  33. package/src/kernel/events.ts +323 -0
  34. package/src/kernel/index.ts +18 -0
  35. package/src/kernel/pipeline.ts +152 -0
  36. package/src/kernel/run-controller.ts +85 -0
  37. package/src/kernel/tokens.ts +21 -0
  38. package/src/security/index.ts +13 -0
  39. package/src/security/permission-policy.ts +273 -0
  40. package/src/session/audit-log.ts +201 -0
  41. package/src/session/auth-service.ts +178 -0
  42. package/src/session/index.ts +26 -0
  43. package/src/session/secret-vault.ts +183 -0
  44. package/src/session/session-store.ts +339 -0
  45. package/src/session/types.ts +100 -0
  46. package/src/types/blocks.ts +56 -0
  47. package/src/types/context.ts +54 -0
  48. package/src/types/errors.ts +359 -0
  49. package/src/types/index.ts +34 -0
  50. package/src/types/provider.ts +58 -0
  51. package/src/types/tool.ts +39 -0
  52. package/src/utils/error.ts +3 -0
  53. package/src/utils/fs.ts +185 -0
  54. package/src/utils/image-resize.ts +76 -0
  55. package/src/utils/ssrf-guard.ts +133 -0
  56. package/src/utils/ulid.ts +72 -0
  57. package/src/utils/version-check.ts +59 -0
  58. package/tests/agent-loop.test.ts +490 -0
  59. package/tests/audit-log.test.ts +199 -0
  60. package/tests/auth-service.test.ts +170 -0
  61. package/tests/blocks.test.ts +79 -0
  62. package/tests/catalog-parser.test.ts +174 -0
  63. package/tests/compactor.test.ts +180 -0
  64. package/tests/container.test.ts +224 -0
  65. package/tests/conversation-state.test.ts +75 -0
  66. package/tests/errors.test.ts +429 -0
  67. package/tests/events-v021.test.ts +60 -0
  68. package/tests/events-v022.test.ts +75 -0
  69. package/tests/events.test.ts +340 -0
  70. package/tests/fixtures/large-image.png +0 -0
  71. package/tests/fixtures/small-image.png +0 -0
  72. package/tests/fs-utils.test.ts +164 -0
  73. package/tests/image-resize.test.ts +51 -0
  74. package/tests/output-serializer.test.ts +79 -0
  75. package/tests/path-resolver.test.ts +91 -0
  76. package/tests/permission-policy.test.ts +174 -0
  77. package/tests/pipeline.test.ts +193 -0
  78. package/tests/provider-factory.test.ts +245 -0
  79. package/tests/provider-runner.test.ts +535 -0
  80. package/tests/retry-policy.test.ts +104 -0
  81. package/tests/run-controller.test.ts +115 -0
  82. package/tests/sanity.test.ts +26 -0
  83. package/tests/schema-validator.test.ts +109 -0
  84. package/tests/secret-scrubber.test.ts +133 -0
  85. package/tests/secret-vault.test.ts +130 -0
  86. package/tests/session-store.test.ts +429 -0
  87. package/tests/ssrf-guard.test.ts +112 -0
  88. package/tests/system-prompt-builder.test.ts +116 -0
  89. package/tests/token-counter.test.ts +163 -0
  90. package/tests/tokens.test.ts +42 -0
  91. package/tests/tool-executor.test.ts +452 -0
  92. package/tests/tool-registry.test.ts +143 -0
  93. package/tests/tracer.test.ts +32 -0
  94. package/tests/ulid.test.ts +53 -0
  95. package/tests/version-check.test.ts +57 -0
  96. package/tsconfig.json +11 -0
  97. package/tsup.config.ts +16 -0
@@ -0,0 +1,224 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { Container, token } from '../src/kernel/container.js';
3
+
4
+ describe('Container', () => {
5
+ describe('bind + resolve', () => {
6
+ it('binds and resolves a simple value', () => {
7
+ const c = new Container();
8
+ const T = token<string>('greeting');
9
+ c.bind(T, () => 'hello');
10
+ expect(c.resolve(T)).toBe('hello');
11
+ });
12
+
13
+ it('resolves dependencies from the container', () => {
14
+ const c = new Container();
15
+ const TUrl = token<string>('url');
16
+ const TClient = token<{ url: string }>('client');
17
+ c.bind(TUrl, () => 'https://api.example.com');
18
+ c.bind(TClient, (container) => ({ url: container.resolve(TUrl) }));
19
+ const client = c.resolve(TClient);
20
+ expect(client.url).toBe('https://api.example.com');
21
+ });
22
+
23
+ it('caches singletons by default', () => {
24
+ const c = new Container();
25
+ let calls = 0;
26
+ const T = token<number>('counter');
27
+ c.bind(T, () => ++calls);
28
+ c.resolve(T);
29
+ c.resolve(T);
30
+ expect(calls).toBe(1);
31
+ });
32
+
33
+ it('non-singleton calls factory each time', () => {
34
+ const c = new Container();
35
+ let calls = 0;
36
+ const T = token<number>('counter');
37
+ c.bind(T, () => ++calls, { singleton: false });
38
+ c.resolve(T);
39
+ c.resolve(T);
40
+ expect(calls).toBe(2);
41
+ });
42
+ });
43
+
44
+ describe('duplicate binding', () => {
45
+ it('throws on duplicate bind', () => {
46
+ const c = new Container();
47
+ const T = token<string>('x');
48
+ c.bind(T, () => 'a');
49
+ expect(() => c.bind(T, () => 'b')).toThrow(/already bound/);
50
+ });
51
+ });
52
+
53
+ describe('override', () => {
54
+ it('replaces an existing binding', () => {
55
+ const c = new Container();
56
+ const T = token<string>('x');
57
+ c.bind(T, () => 'old');
58
+ c.override(T, () => 'new');
59
+ expect(c.resolve(T)).toBe('new');
60
+ });
61
+
62
+ it('throws when overriding unbound token', () => {
63
+ const c = new Container();
64
+ const T = token<string>('x');
65
+ expect(() => c.override(T, () => 'x')).toThrow(/not bound/);
66
+ });
67
+
68
+ it('preserves singleton setting from existing', () => {
69
+ const c = new Container();
70
+ const T = token<number>('n');
71
+ c.bind(T, () => 1, { singleton: false });
72
+ c.override(T, () => 2);
73
+ const inspect = c.inspect(T);
74
+ expect(inspect?.singleton).toBe(false);
75
+ });
76
+ });
77
+
78
+ describe('decorate', () => {
79
+ it('stacks decorators on resolve', () => {
80
+ const c = new Container();
81
+ const T = token<number>('n');
82
+ c.bind(T, () => 1);
83
+ c.decorate(T, (v) => v + 10);
84
+ c.decorate(T, (v) => v + 100);
85
+ expect(c.resolve(T)).toBe(111);
86
+ });
87
+
88
+ it('clears cache on decorate', () => {
89
+ const c = new Container();
90
+ const T = token<number>('n');
91
+ c.bind(T, () => 1);
92
+ c.resolve(T);
93
+ expect(c.inspect(T)?.cached).toBe(true);
94
+ c.decorate(T, (v) => v + 1);
95
+ expect(c.inspect(T)?.cached).toBe(false);
96
+ });
97
+
98
+ it('throws when decorating unbound token', () => {
99
+ const c = new Container();
100
+ const T = token<string>('x');
101
+ expect(() => c.decorate(T, (v) => v)).toThrow(/not bound/);
102
+ });
103
+ });
104
+
105
+ describe('circular dependency', () => {
106
+ it('detects A → B → A', () => {
107
+ const c = new Container();
108
+ const TA = token<string>('A');
109
+ const TB = token<string>('B');
110
+ c.bind(TA, (container) => container.resolve(TB));
111
+ c.bind(TB, (container) => container.resolve(TA));
112
+ expect(() => c.resolve(TA)).toThrow(/circular dependency/);
113
+ });
114
+ });
115
+
116
+ describe('has + safeResolve', () => {
117
+ it('has returns true for bound tokens', () => {
118
+ const c = new Container();
119
+ const T = token<string>('x');
120
+ c.bind(T, () => 'val');
121
+ expect(c.has(T)).toBe(true);
122
+ });
123
+
124
+ it('has returns false for unbound tokens', () => {
125
+ const c = new Container();
126
+ const T = token<string>('x');
127
+ expect(c.has(T)).toBe(false);
128
+ });
129
+
130
+ it('safeResolve returns undefined for unbound', () => {
131
+ const c = new Container();
132
+ const T = token<string>('x');
133
+ expect(c.safeResolve(T)).toBeUndefined();
134
+ });
135
+
136
+ it('safeResolve returns value for bound', () => {
137
+ const c = new Container();
138
+ const T = token<string>('x');
139
+ c.bind(T, () => 'val');
140
+ expect(c.safeResolve(T)).toBe('val');
141
+ });
142
+ });
143
+
144
+ describe('unbind', () => {
145
+ it('removes a binding', () => {
146
+ const c = new Container();
147
+ const T = token<string>('x');
148
+ c.bind(T, () => 'val');
149
+ expect(c.unbind(T)).toBe(true);
150
+ expect(c.has(T)).toBe(false);
151
+ });
152
+
153
+ it('returns false for unbound token', () => {
154
+ const c = new Container();
155
+ const T = token<string>('x');
156
+ expect(c.unbind(T)).toBe(false);
157
+ });
158
+ });
159
+
160
+ describe('clear', () => {
161
+ it('removes all bindings', () => {
162
+ const c = new Container();
163
+ c.bind(token('a'), () => 1);
164
+ c.bind(token('b'), () => 2);
165
+ c.clear();
166
+ expect(c.list()).toHaveLength(0);
167
+ });
168
+ });
169
+
170
+ describe('list', () => {
171
+ it('lists all bindings with owners', () => {
172
+ const c = new Container();
173
+ const TA = token<string>('A');
174
+ const TB = token<string>('B');
175
+ c.bind(TA, () => 'a', { owner: 'plugin-x' });
176
+ c.bind(TB, () => 'b');
177
+ const items = c.list();
178
+ expect(items).toHaveLength(2);
179
+ const a = items.find((i) => i.token === TA);
180
+ expect(a?.owner).toBe('plugin-x');
181
+ });
182
+ });
183
+
184
+ describe('inspect', () => {
185
+ it('returns null for unbound token', () => {
186
+ const c = new Container();
187
+ expect(c.inspect(token('x'))).toBeNull();
188
+ });
189
+
190
+ it('returns binding metadata', () => {
191
+ const c = new Container();
192
+ const T = token<number>('n');
193
+ c.bind(T, () => 1);
194
+ c.decorate(T, (v) => v + 1);
195
+ const info = c.inspect(T);
196
+ expect(info?.owner).toBe('core+core');
197
+ expect(info?.singleton).toBe(true);
198
+ expect(info?.decoratorCount).toBe(1);
199
+ expect(info?.cached).toBe(false);
200
+ });
201
+
202
+ it('shows cached after resolve', () => {
203
+ const c = new Container();
204
+ const T = token<number>('n');
205
+ c.bind(T, () => 1);
206
+ c.resolve(T);
207
+ expect(c.inspect(T)?.cached).toBe(true);
208
+ });
209
+ });
210
+
211
+ describe('ownerOf', () => {
212
+ it('returns owner for bound token', () => {
213
+ const c = new Container();
214
+ const T = token<string>('x');
215
+ c.bind(T, () => 'v', { owner: 'plugin-y' });
216
+ expect(c.ownerOf(T)).toBe('plugin-y');
217
+ });
218
+
219
+ it('returns undefined for unbound', () => {
220
+ const c = new Container();
221
+ expect(c.ownerOf(token('x'))).toBeUndefined();
222
+ });
223
+ });
224
+ });
@@ -0,0 +1,75 @@
1
+ import { describe, it, expect, vi } from 'vitest';
2
+ import { DefaultConversationState } from '../src/agent/conversation-state.js';
3
+ import type { Message } from '../src/types/blocks.js';
4
+
5
+ function msg(text: string): Message {
6
+ return { role: 'user', content: [{ type: 'text', text }] };
7
+ }
8
+
9
+ describe('DefaultConversationState', () => {
10
+ it('starts empty', () => {
11
+ const s = new DefaultConversationState();
12
+ expect(s.messages).toHaveLength(0);
13
+ });
14
+
15
+ it('appendMessage adds and is visible via messages', () => {
16
+ const s = new DefaultConversationState();
17
+ s.appendMessage(msg('a'));
18
+ s.appendMessage(msg('b'));
19
+ expect(s.messages).toHaveLength(2);
20
+ expect(s.messages[1]!.content).toEqual([{ type: 'text', text: 'b' }]);
21
+ });
22
+
23
+ it('replaceMessages swaps the whole array', () => {
24
+ const s = new DefaultConversationState();
25
+ s.appendMessage(msg('old'));
26
+ s.replaceMessages([msg('new1'), msg('new2')]);
27
+ expect(s.messages).toHaveLength(2);
28
+ const first = s.messages[0]!;
29
+ expect((first.content as [{ type: 'text'; text: string }])[0]!.text).toBe('new1');
30
+ });
31
+
32
+ it('replaceMessages with empty clears', () => {
33
+ const s = new DefaultConversationState();
34
+ s.appendMessage(msg('x'));
35
+ s.replaceMessages([]);
36
+ expect(s.messages).toHaveLength(0);
37
+ });
38
+
39
+ it('onChange fires on append and replace', () => {
40
+ const s = new DefaultConversationState();
41
+ const fn = vi.fn();
42
+ s.onChange(fn);
43
+ s.appendMessage(msg('a'));
44
+ s.replaceMessages([msg('b')]);
45
+ expect(fn).toHaveBeenCalledTimes(2);
46
+ });
47
+
48
+ it('onChange unsubscribe stops further calls', () => {
49
+ const s = new DefaultConversationState();
50
+ const fn = vi.fn();
51
+ const unsub = s.onChange(fn);
52
+ s.appendMessage(msg('a'));
53
+ unsub();
54
+ s.appendMessage(msg('b'));
55
+ expect(fn).toHaveBeenCalledTimes(1);
56
+ });
57
+
58
+ it('a throwing listener does not block siblings', () => {
59
+ const s = new DefaultConversationState();
60
+ const ok = vi.fn();
61
+ s.onChange(() => {
62
+ throw new Error('boom');
63
+ });
64
+ s.onChange(ok);
65
+ s.appendMessage(msg('a'));
66
+ expect(ok).toHaveBeenCalledTimes(1);
67
+ });
68
+
69
+ it('push on the live array mutates state (AgentContext.messages contract)', () => {
70
+ const s = new DefaultConversationState();
71
+ const live = s.messages as Message[];
72
+ live.push(msg('direct'));
73
+ expect(s.messages).toHaveLength(1);
74
+ });
75
+ });