@flink-app/flink 2.0.0-alpha.61 → 2.0.0-alpha.63

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.
@@ -0,0 +1,151 @@
1
+ "use strict";
2
+ var __assign = (this && this.__assign) || function () {
3
+ __assign = Object.assign || function(t) {
4
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
5
+ s = arguments[i];
6
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
+ t[p] = s[p];
8
+ }
9
+ return t;
10
+ };
11
+ return __assign.apply(this, arguments);
12
+ };
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || function (mod) {
30
+ if (mod && mod.__esModule) return mod;
31
+ var result = {};
32
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
33
+ __setModuleDefault(result, mod);
34
+ return result;
35
+ };
36
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
37
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
38
+ return new (P || (P = Promise))(function (resolve, reject) {
39
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
40
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
41
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
42
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
43
+ });
44
+ };
45
+ var __generator = (this && this.__generator) || function (thisArg, body) {
46
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
47
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
48
+ function verb(n) { return function (v) { return step([n, v]); }; }
49
+ function step(op) {
50
+ if (f) throw new TypeError("Generator is already executing.");
51
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
52
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
53
+ if (y = 0, t) op = [op[0] & 2, t.value];
54
+ switch (op[0]) {
55
+ case 0: case 1: t = op; break;
56
+ case 4: _.label++; return { value: op[1], done: false };
57
+ case 5: _.label++; y = op[1]; op = [0]; continue;
58
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
59
+ default:
60
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
61
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
62
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
63
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
64
+ if (t[2]) _.ops.pop();
65
+ _.trys.pop(); continue;
66
+ }
67
+ op = body.call(thisArg, _);
68
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
69
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
70
+ }
71
+ };
72
+ var __importDefault = (this && this.__importDefault) || function (mod) {
73
+ return (mod && mod.__esModule) ? mod : { "default": mod };
74
+ };
75
+ Object.defineProperty(exports, "__esModule", { value: true });
76
+ exports.resolveInstructionsReturn = resolveInstructionsReturn;
77
+ var fs = __importStar(require("fs"));
78
+ var path = __importStar(require("path"));
79
+ var handlebars_1 = __importDefault(require("handlebars"));
80
+ var fileCache = new Map();
81
+ /**
82
+ * Resolve a file path relative to project root (process.cwd()).
83
+ * Leading `./` and `/` are normalised away — all paths are treated as project-root-relative.
84
+ */
85
+ function resolveFilePath(filePath) {
86
+ // Strip leading slash so "/foo.md" behaves the same as "foo.md"
87
+ var normalised = filePath.replace(/^\/+/, "");
88
+ return path.resolve(process.cwd(), normalised);
89
+ }
90
+ function loadFile(filePath) {
91
+ var resolvedPath = resolveFilePath(filePath);
92
+ try {
93
+ var stats = fs.statSync(resolvedPath);
94
+ var mtime = stats.mtimeMs;
95
+ var cached = fileCache.get(resolvedPath);
96
+ if (cached && cached.mtime === mtime) {
97
+ return cached;
98
+ }
99
+ var content = fs.readFileSync(resolvedPath, "utf-8");
100
+ var entry = { content: content, mtime: mtime };
101
+ fileCache.set(resolvedPath, entry);
102
+ return entry;
103
+ }
104
+ catch (err) {
105
+ if (err.code === "ENOENT") {
106
+ throw new Error("Agent instructions file not found: ".concat(resolvedPath, " (from: ").concat(filePath, ")"));
107
+ }
108
+ throw new Error("Failed to load agent instructions file: ".concat(resolvedPath, " - ").concat(err.message));
109
+ }
110
+ }
111
+ function renderTemplate(entry, data) {
112
+ if (entry.hasTemplateExpressions === false) {
113
+ return entry.content;
114
+ }
115
+ if (!entry.compiledTemplate) {
116
+ entry.hasTemplateExpressions = /\{\{/.test(entry.content);
117
+ if (!entry.hasTemplateExpressions) {
118
+ return entry.content;
119
+ }
120
+ entry.compiledTemplate = handlebars_1.default.compile(entry.content);
121
+ }
122
+ return entry.compiledTemplate(data);
123
+ }
124
+ var TEXT_FILE_EXTENSIONS = [".md", ".txt", ".yaml", ".yml", ".xml", ".toml", ".ini", ".json", ".html", ".htm"];
125
+ function isTextFilePath(value) {
126
+ var trimmed = value.trimEnd().toLowerCase();
127
+ return TEXT_FILE_EXTENSIONS.some(function (ext) { return trimmed.endsWith(ext); });
128
+ }
129
+ /**
130
+ * Resolve an InstructionsReturn value to a plain string.
131
+ * @internal Used by FlinkAgent.toAgentProps()
132
+ */
133
+ function resolveInstructionsReturn(result, ctx, agentContext) {
134
+ return __awaiter(this, void 0, void 0, function () {
135
+ var entry_1, templateData_1, file, params, entry, templateData;
136
+ return __generator(this, function (_a) {
137
+ if (typeof result === "string") {
138
+ if (isTextFilePath(result)) {
139
+ entry_1 = loadFile(result.trimEnd());
140
+ templateData_1 = { ctx: ctx, agentContext: agentContext, user: agentContext === null || agentContext === void 0 ? void 0 : agentContext.user };
141
+ return [2 /*return*/, renderTemplate(entry_1, templateData_1)];
142
+ }
143
+ return [2 /*return*/, result];
144
+ }
145
+ file = result.file, params = result.params;
146
+ entry = loadFile(file);
147
+ templateData = __assign(__assign({}, params), { ctx: ctx, agentContext: agentContext, user: agentContext === null || agentContext === void 0 ? void 0 : agentContext.user });
148
+ return [2 /*return*/, renderTemplate(entry, templateData)];
149
+ });
150
+ });
151
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flink-app/flink",
3
- "version": "2.0.0-alpha.61",
3
+ "version": "2.0.0-alpha.63",
4
4
  "description": "Typescript only framework for creating REST-like APIs on top of Express and mongodb",
5
5
  "types": "dist/src/index.d.ts",
6
6
  "main": "dist/src/index.js",
@@ -166,7 +166,7 @@ describe("Agent Descendant Detection", () => {
166
166
  export default class MyAgent extends InMemoryConversationAgent<any> {
167
167
  id = "my-agent";
168
168
  description = "Test agent";
169
- instructions = "You are helpful";
169
+ instructions() { return "You are helpful"; }
170
170
  }
171
171
  `;
172
172
  testAgentDetection(source, true);
@@ -182,7 +182,7 @@ describe("Agent Descendant Detection", () => {
182
182
  export default class MyAgent extends InMemoryConversationAgent<AppContext> {
183
183
  id = "my-agent";
184
184
  description = "Test agent";
185
- instructions = "You are helpful";
185
+ instructions() { return "You are helpful"; }
186
186
  }
187
187
  `;
188
188
  testAgentDetection(source, true);
@@ -158,7 +158,7 @@ describe("AgentRunner", () => {
158
158
  input: { city: "Stockholm" },
159
159
  ctx: mockCtx,
160
160
  user: undefined,
161
- permissions: [],
161
+ permissions: undefined,
162
162
  conversationCtx: undefined,
163
163
  });
164
164
  });
@@ -34,7 +34,7 @@ describe("Conversation Hooks", () => {
34
34
  class TestAgent extends FlinkAgent<FlinkContext> {
35
35
  id = "test-agent";
36
36
  description = "Test agent";
37
- instructions = "Test instructions";
37
+ instructions() { return "Test instructions"; }
38
38
  tools: string[] = [];
39
39
 
40
40
  protected beforeRun = beforeRunSpy;
@@ -71,7 +71,7 @@ describe("Conversation Hooks", () => {
71
71
  class TestAgent extends FlinkAgent<FlinkContext> {
72
72
  id = "test-agent";
73
73
  description = "Test agent";
74
- instructions = "Test instructions";
74
+ instructions() { return "Test instructions"; }
75
75
  tools: string[] = [];
76
76
 
77
77
  protected async beforeRun(input: AgentExecuteInput, context: AgentExecuteContext) {
@@ -139,7 +139,7 @@ describe("Conversation Hooks", () => {
139
139
  class TestAgent extends FlinkAgent<FlinkContext> {
140
140
  id = "test-agent";
141
141
  description = "Test agent";
142
- instructions = "Test instructions";
142
+ instructions() { return "Test instructions"; }
143
143
  tools: string[] = ["test_tool"];
144
144
 
145
145
  protected onStep = onStepSpy;
@@ -177,7 +177,7 @@ describe("Conversation Hooks", () => {
177
177
  class TestAgent extends FlinkAgent<FlinkContext> {
178
178
  id = "test-agent";
179
179
  description = "Test agent";
180
- instructions = "Test instructions";
180
+ instructions() { return "Test instructions"; }
181
181
  tools: string[] = [];
182
182
 
183
183
  protected afterRun = afterRunSpy;
@@ -216,7 +216,7 @@ describe("Conversation Hooks", () => {
216
216
  class TestAgent extends FlinkAgent<FlinkContext> {
217
217
  id = "test-agent";
218
218
  description = "Test agent";
219
- instructions = "Test instructions";
219
+ instructions() { return "Test instructions"; }
220
220
  tools: string[] = [];
221
221
 
222
222
  protected beforeRun = beforeRunSpy;
@@ -31,7 +31,7 @@ describe("FlinkAgent", () => {
31
31
  class TestAgent extends FlinkAgent<FlinkContext> {
32
32
  id = "test-agent";
33
33
  description = "Test agent";
34
- instructions = "Test instructions";
34
+ instructions() { return "Test instructions"; }
35
35
  tools: string[] = [];
36
36
 
37
37
  async query(message: string) {
@@ -131,7 +131,7 @@ describe("FlinkAgent", () => {
131
131
  class TestAgent extends FlinkAgent<FlinkContext> {
132
132
  id = "test-agent";
133
133
  description = "Test agent";
134
- instructions = "Test instructions";
134
+ instructions() { return "Test instructions"; }
135
135
  tools: string[] = ["public_tool", "admin_tool"];
136
136
 
137
137
  async query(message: string) {
@@ -250,7 +250,7 @@ describe("FlinkAgent", () => {
250
250
  class TestAgentWithToolRef extends FlinkAgent<FlinkContext> {
251
251
  id = "test-agent";
252
252
  description = "Test agent";
253
- instructions = "Test instructions";
253
+ instructions() { return "Test instructions"; }
254
254
  tools = [mockToolFile]; // Direct tool file reference!
255
255
 
256
256
  async query(message: string) {
@@ -290,7 +290,7 @@ describe("FlinkAgent", () => {
290
290
  class MixedToolsAgent extends FlinkAgent<FlinkContext> {
291
291
  id = "test-agent";
292
292
  description = "Test agent";
293
- instructions = "Test instructions";
293
+ instructions() { return "Test instructions"; }
294
294
  tools = [
295
295
  mockToolFile, // File reference
296
296
  "tool-by-string", // String ID
@@ -317,7 +317,7 @@ describe("FlinkAgent", () => {
317
317
  class StaticAgent extends FlinkAgent<FlinkContext> {
318
318
  id = "test-agent";
319
319
  description = "Static agent";
320
- instructions = "Static instructions";
320
+ instructions() { return "Static instructions"; }
321
321
  tools: string[] = [];
322
322
 
323
323
  async query(message: string) {
@@ -342,9 +342,9 @@ describe("FlinkAgent", () => {
342
342
  class DynamicAgent extends FlinkAgent<FlinkContext> {
343
343
  id = "test-agent";
344
344
  description = "Dynamic agent";
345
- instructions = (ctx: FlinkContext, agentContext: any) => {
345
+ instructions(ctx: FlinkContext, agentContext: any) {
346
346
  return `You are a support agent for user ${agentContext.user?.name || "unknown"}`;
347
- };
347
+ }
348
348
  tools: string[] = [];
349
349
 
350
350
  async query(message: string) {
@@ -380,13 +380,13 @@ describe("FlinkAgent", () => {
380
380
  class DatabaseAgent extends FlinkAgent<any> {
381
381
  id = "test-agent";
382
382
  description = "Database agent";
383
- instructions = async (ctx: any, agentContext: any) => {
383
+ async instructions(ctx: any, agentContext: any) {
384
384
  if (!agentContext.user) {
385
385
  return "You are a support agent.";
386
386
  }
387
387
  const profile = await ctx.repos.userRepo.getById(agentContext.user.id);
388
388
  return `You are a support agent. Customer: ${profile.name}, Tier: ${profile.tier}`;
389
- };
389
+ }
390
390
  tools: string[] = [];
391
391
 
392
392
  async query(message: string) {
@@ -415,10 +415,10 @@ describe("FlinkAgent", () => {
415
415
  class CachedAgent extends FlinkAgent<FlinkContext> {
416
416
  id = "test-agent";
417
417
  description = "Cached agent";
418
- instructions = (ctx: FlinkContext, agentContext: any) => {
418
+ instructions(ctx: FlinkContext, agentContext: any) {
419
419
  callCount++;
420
420
  return `Instructions for user ${agentContext.user?.name || "unknown"}`;
421
- };
421
+ }
422
422
  tools: string[] = [];
423
423
 
424
424
  async query(message: string) {
@@ -461,10 +461,10 @@ describe("FlinkAgent", () => {
461
461
  class ConversationAgent extends FlinkAgent<FlinkContext> {
462
462
  id = "test-agent";
463
463
  description = "Conversation agent";
464
- instructions = (ctx: FlinkContext, agentContext: any) => {
464
+ instructions(ctx: FlinkContext, agentContext: any) {
465
465
  capturedContext = agentContext;
466
466
  return `Conversation: ${agentContext.conversationId || "new"}`;
467
- };
467
+ }
468
468
  tools: string[] = [];
469
469
 
470
470
  async query(message: string, conversationId?: string) {
@@ -489,9 +489,9 @@ describe("FlinkAgent", () => {
489
489
  class ErrorAgent extends FlinkAgent<FlinkContext> {
490
490
  id = "test-agent";
491
491
  description = "Error agent";
492
- instructions = () => {
492
+ instructions(): string {
493
493
  throw new Error("Database unavailable");
494
- };
494
+ }
495
495
  tools: string[] = [];
496
496
 
497
497
  async query(message: string) {
@@ -518,7 +518,7 @@ describe("FlinkAgent", () => {
518
518
  class TestAgent extends FlinkAgent<FlinkContext, TestConversationContext> {
519
519
  id = "test-agent";
520
520
  description = "Test agent";
521
- instructions = "Test instructions";
521
+ instructions() { return "Test instructions"; }
522
522
  tools: string[] = [];
523
523
 
524
524
  async query(message: string) {
@@ -0,0 +1,123 @@
1
+ import { FlinkApp } from "../src/FlinkApp";
2
+ import { FlinkContext } from "../src/FlinkContext";
3
+ import { GetHandler, Handler, HttpMethod } from "../src/FlinkHttpHandler";
4
+
5
+ const request = require("supertest");
6
+
7
+ interface TestContext extends FlinkContext {}
8
+
9
+ const resSchema = {
10
+ type: "object",
11
+ properties: {
12
+ id: { type: "string" },
13
+ },
14
+ required: ["id"],
15
+ };
16
+
17
+ describe("FlinkApp response validation when handler returns no data", () => {
18
+ let app: FlinkApp<TestContext>;
19
+
20
+ afterEach(async () => {
21
+ if (app && app.started) {
22
+ await app.stop();
23
+ }
24
+ });
25
+
26
+ it("should return 500 bad response when handler returns undefined data with a response schema", async () => {
27
+ const handler: GetHandler<TestContext, any> = async () => {
28
+ return { status: 200 } as any; // no data
29
+ };
30
+
31
+ app = new FlinkApp<TestContext>({ name: "test-undefined-data", port: 4200 });
32
+ await app.start();
33
+
34
+ app.addHandler({
35
+ default: handler,
36
+ Route: { method: HttpMethod.get, path: "/test", resSchema },
37
+ });
38
+
39
+ const response = await request(app.expressApp).get("/test");
40
+
41
+ expect(response.status).toBe(500);
42
+ expect(response.body.error.title).toBe("Bad response");
43
+ expect(response.body.error.detail).toContain("no data");
44
+ });
45
+
46
+ it("should NOT return 500 when handler returns status 204 with no data (even if schema is defined)", async () => {
47
+ const handler: Handler<TestContext, any, any> = async () => {
48
+ return { status: 204 } as any;
49
+ };
50
+
51
+ app = new FlinkApp<TestContext>({ name: "test-204-no-data", port: 4201 });
52
+ await app.start();
53
+
54
+ app.addHandler({
55
+ default: handler,
56
+ Route: { method: HttpMethod.patch, path: "/test", resSchema },
57
+ });
58
+
59
+ const response = await request(app.expressApp).patch("/test");
60
+
61
+ expect(response.status).toBe(204);
62
+ });
63
+
64
+ it("should validate normally when handler returns valid data", async () => {
65
+ const handler: GetHandler<TestContext, any> = async () => {
66
+ return { data: { id: "abc-123" } };
67
+ };
68
+
69
+ app = new FlinkApp<TestContext>({ name: "test-valid-data", port: 4202 });
70
+ await app.start();
71
+
72
+ app.addHandler({
73
+ default: handler,
74
+ Route: { method: HttpMethod.get, path: "/test", resSchema },
75
+ });
76
+
77
+ const response = await request(app.expressApp).get("/test");
78
+
79
+ expect(response.status).toBe(200);
80
+ expect(response.body.data.id).toBe("abc-123");
81
+ });
82
+
83
+ it("should return 500 bad response when handler returns invalid data against schema", async () => {
84
+ const handler: GetHandler<TestContext, any> = async () => {
85
+ return { data: { wrongField: 123 } }; // missing required "id"
86
+ };
87
+
88
+ app = new FlinkApp<TestContext>({ name: "test-invalid-data", port: 4203 });
89
+ await app.start();
90
+
91
+ app.addHandler({
92
+ default: handler,
93
+ Route: { method: HttpMethod.get, path: "/test", resSchema },
94
+ });
95
+
96
+ const response = await request(app.expressApp).get("/test");
97
+
98
+ expect(response.status).toBe(500);
99
+ expect(response.body.error.title).toBe("Bad response");
100
+ });
101
+
102
+ it("should not crash the server when handler returns undefined data", async () => {
103
+ const handler: GetHandler<TestContext, any> = async () => {
104
+ return { status: 200 } as any;
105
+ };
106
+
107
+ app = new FlinkApp<TestContext>({ name: "test-no-crash", port: 4204 });
108
+ await app.start();
109
+
110
+ app.addHandler({
111
+ default: handler,
112
+ Route: { method: HttpMethod.get, path: "/test", resSchema },
113
+ });
114
+
115
+ // First request triggers the bad response
116
+ await request(app.expressApp).get("/test");
117
+
118
+ // Server should still be running and able to handle further requests
119
+ const second = await request(app.expressApp).get("/test");
120
+ expect(second.status).toBe(500); // still returns 500, but doesn't crash
121
+ expect(app.started).toBe(true);
122
+ });
123
+ });
@@ -0,0 +1,95 @@
1
+ import { FlinkApp, autoRegisteredJobs } from "../src/FlinkApp";
2
+ import { FlinkContext } from "../src/FlinkContext";
3
+ import { FlinkJobFile } from "../src/FlinkJob";
4
+
5
+ interface TestContext extends FlinkContext {}
6
+
7
+ describe("FlinkJob error handling", () => {
8
+ let app: FlinkApp<TestContext>;
9
+ let consoleErrorSpy: jasmine.Spy;
10
+
11
+ beforeEach(() => {
12
+ consoleErrorSpy = spyOn(console, "error");
13
+ });
14
+
15
+ afterEach(async () => {
16
+ autoRegisteredJobs.length = 0;
17
+ if (app?.started) {
18
+ await app.stop();
19
+ }
20
+ });
21
+
22
+ it("should catch and log errors from afterDelay 0ms jobs without crashing", async () => {
23
+ const job: FlinkJobFile = {
24
+ Job: { id: "failing-job-0ms", afterDelay: "0ms" },
25
+ default: async () => {
26
+ throw new Error("Job error 0ms");
27
+ },
28
+ };
29
+
30
+ autoRegisteredJobs.push(job);
31
+
32
+ app = new FlinkApp<TestContext>({ name: "test-job-errors-0ms", disableHttpServer: true });
33
+ await app.start();
34
+
35
+ await new Promise((resolve) => setTimeout(resolve, 50));
36
+
37
+ expect(consoleErrorSpy).toHaveBeenCalled();
38
+ });
39
+
40
+ it("should catch and log errors from afterDelay jobs without crashing", async () => {
41
+ const job: FlinkJobFile = {
42
+ Job: { id: "failing-job-delay", afterDelay: "10ms" },
43
+ default: async () => {
44
+ throw new Error("Job error with delay");
45
+ },
46
+ };
47
+
48
+ autoRegisteredJobs.push(job);
49
+
50
+ app = new FlinkApp<TestContext>({ name: "test-job-errors-delay", disableHttpServer: true });
51
+ await app.start();
52
+
53
+ await new Promise((resolve) => setTimeout(resolve, 100));
54
+
55
+ expect(consoleErrorSpy).toHaveBeenCalled();
56
+ });
57
+
58
+ it("should catch and log errors from interval jobs without crashing", async () => {
59
+ const job: FlinkJobFile = {
60
+ Job: { id: "failing-interval-job", interval: "10ms" },
61
+ default: async () => {
62
+ throw new Error("Interval job error");
63
+ },
64
+ };
65
+
66
+ autoRegisteredJobs.push(job);
67
+
68
+ app = new FlinkApp<TestContext>({ name: "test-job-errors-interval", disableHttpServer: true });
69
+ await app.start();
70
+
71
+ await new Promise((resolve) => setTimeout(resolve, 100));
72
+
73
+ expect(consoleErrorSpy).toHaveBeenCalled();
74
+ });
75
+
76
+ it("should run afterDelay 0ms job exactly once", async () => {
77
+ let runCount = 0;
78
+
79
+ const job: FlinkJobFile = {
80
+ Job: { id: "once-job-0ms", afterDelay: "0ms" },
81
+ default: async () => {
82
+ runCount++;
83
+ },
84
+ };
85
+
86
+ autoRegisteredJobs.push(job);
87
+
88
+ app = new FlinkApp<TestContext>({ name: "test-job-once-0ms", disableHttpServer: true });
89
+ await app.start();
90
+
91
+ await new Promise((resolve) => setTimeout(resolve, 100));
92
+
93
+ expect(runCount).toBe(1);
94
+ });
95
+ });
@@ -15,7 +15,7 @@ describe("Streaming Integration", () => {
15
15
  class StreamingTestAgent extends FlinkAgent<FlinkContext> {
16
16
  id = "streaming-test-agent";
17
17
  description = "Test agent for streaming";
18
- instructions = "You are a test agent";
18
+ instructions() { return "You are a test agent"; }
19
19
  tools = [];
20
20
 
21
21
  async query(message: string) {
@@ -7,7 +7,7 @@ import { LLMAdapter, LLMMessage } from "../../src/ai/LLMAdapter";
7
7
  class CompactingAgent extends FlinkAgent<FlinkContext> {
8
8
  id = "compacting-agent";
9
9
  description = "Test agent with context compaction";
10
- instructions = "You are a test assistant";
10
+ instructions() { return "You are a test assistant"; }
11
11
 
12
12
  setCallbacks(
13
13
  shouldCompact?: (messages: LLMMessage[], step: number) => boolean | Promise<boolean>,
@@ -9,7 +9,7 @@ import {
9
9
  class MockConversationAgent extends ConversationAgent<FlinkContext> {
10
10
  id = "mock-agent";
11
11
  description = "Mock agent for testing";
12
- instructions = "You are a test agent";
12
+ instructions() { return "You are a test agent"; }
13
13
  tools = [];
14
14
 
15
15
  // Spy methods
@@ -3,7 +3,7 @@ import { InMemoryConversationAgent, FlinkContext } from "../../src";
3
3
  class TestAgent extends InMemoryConversationAgent<FlinkContext> {
4
4
  id = "test-agent";
5
5
  description = "Test agent";
6
- instructions = "You are a test agent";
6
+ instructions() { return "You are a test agent"; }
7
7
  tools = [];
8
8
  }
9
9
 
@@ -8,15 +8,17 @@ export default class TestAgent extends FlinkAgent<FlinkContext> {
8
8
  // Load instructions from file using project-root path
9
9
  // Note: For relative paths (./) to work, they must be called from the agent file itself
10
10
  // Since this is loaded via require() from test, we use project-root path
11
- instructions = agentInstructions(
12
- "spec/fixtures/agent-instructions/template.md",
13
- (ctx, agentContext) => ({
14
- tier: agentContext.user?.tier || "basic",
15
- isPremium: agentContext.user?.tier === "premium",
16
- isBusinessHours: true,
17
- tools: ["test-tool-1", "test-tool-2"],
18
- })
19
- );
11
+ instructions(ctx: FlinkContext, agentContext: any) {
12
+ return agentInstructions(
13
+ "spec/fixtures/agent-instructions/template.md",
14
+ (ctx, agentContext) => ({
15
+ tier: agentContext.user?.tier || "basic",
16
+ isPremium: agentContext.user?.tier === "premium",
17
+ isBusinessHours: true,
18
+ tools: ["test-tool-1", "test-tool-2"],
19
+ })
20
+ )(ctx, agentContext);
21
+ }
20
22
 
21
23
  tools = [];
22
24
  }