@stigmer/react 3.1.13 → 3.1.15
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/agent/AgentDetailView.d.ts +9 -9
- package/agent/AgentDetailView.d.ts.map +1 -1
- package/agent/AgentDetailView.js +23 -31
- package/agent/AgentDetailView.js.map +1 -1
- package/agent-instance/AgentInstanceEmptyState.d.ts +4 -1
- package/agent-instance/AgentInstanceEmptyState.d.ts.map +1 -1
- package/agent-instance/AgentInstanceEmptyState.js +12 -5
- package/agent-instance/AgentInstanceEmptyState.js.map +1 -1
- package/agent-instance/AgentInstanceList.d.ts +13 -2
- package/agent-instance/AgentInstanceList.d.ts.map +1 -1
- package/agent-instance/AgentInstanceList.js +8 -4
- package/agent-instance/AgentInstanceList.js.map +1 -1
- package/agent-instance/useAgentInstances.d.ts +10 -3
- package/agent-instance/useAgentInstances.d.ts.map +1 -1
- package/agent-instance/useAgentInstances.js +12 -4
- package/agent-instance/useAgentInstances.js.map +1 -1
- package/button/Button.d.ts +54 -0
- package/button/Button.d.ts.map +1 -0
- package/button/Button.js +42 -0
- package/button/Button.js.map +1 -0
- package/button/index.d.ts +3 -0
- package/button/index.d.ts.map +1 -0
- package/button/index.js +2 -0
- package/button/index.js.map +1 -0
- package/empty-state/EmptyState.d.ts.map +1 -1
- package/empty-state/EmptyState.js +8 -3
- package/empty-state/EmptyState.js.map +1 -1
- package/empty-state/types.d.ts +2 -0
- package/empty-state/types.d.ts.map +1 -1
- package/execution/index.d.ts +1 -1
- package/execution/index.d.ts.map +1 -1
- package/execution/index.js.map +1 -1
- package/execution/useCreateAgentExecution.d.ts +81 -9
- package/execution/useCreateAgentExecution.d.ts.map +1 -1
- package/execution/useCreateAgentExecution.js +42 -4
- package/execution/useCreateAgentExecution.js.map +1 -1
- package/index.d.ts +5 -3
- package/index.d.ts.map +1 -1
- package/index.js +4 -2
- package/index.js.map +1 -1
- package/package.json +4 -4
- package/session/useNewSessionFlow.d.ts +6 -3
- package/session/useNewSessionFlow.d.ts.map +1 -1
- package/session/useNewSessionFlow.js +34 -35
- package/session/useNewSessionFlow.js.map +1 -1
- package/sharing/AgentShareList.d.ts +46 -0
- package/sharing/AgentShareList.d.ts.map +1 -0
- package/sharing/AgentShareList.js +159 -0
- package/sharing/AgentShareList.js.map +1 -0
- package/sharing/ShareAgentDialog.d.ts +38 -25
- package/sharing/ShareAgentDialog.d.ts.map +1 -1
- package/sharing/ShareAgentDialog.js +131 -62
- package/sharing/ShareAgentDialog.js.map +1 -1
- package/sharing/index.d.ts +10 -8
- package/sharing/index.d.ts.map +1 -1
- package/sharing/index.js +5 -4
- package/sharing/index.js.map +1 -1
- package/sharing/useAgentShares.d.ts +48 -0
- package/sharing/useAgentShares.d.ts.map +1 -0
- package/sharing/useAgentShares.js +44 -0
- package/sharing/useAgentShares.js.map +1 -0
- package/sharing/useCanCreateAgentShare.d.ts +43 -0
- package/sharing/useCanCreateAgentShare.d.ts.map +1 -0
- package/sharing/useCanCreateAgentShare.js +44 -0
- package/sharing/useCanCreateAgentShare.js.map +1 -0
- package/sharing/useDeleteAgentShare.d.ts +42 -0
- package/sharing/useDeleteAgentShare.d.ts.map +1 -0
- package/sharing/useDeleteAgentShare.js +45 -0
- package/sharing/useDeleteAgentShare.js.map +1 -0
- package/sharing/useSaveAgentShare.d.ts +43 -18
- package/sharing/useSaveAgentShare.d.ts.map +1 -1
- package/sharing/useSaveAgentShare.js +55 -22
- package/sharing/useSaveAgentShare.js.map +1 -1
- package/src/agent/AgentDetailView.tsx +37 -43
- package/src/agent-instance/AgentInstanceEmptyState.tsx +24 -43
- package/src/agent-instance/AgentInstanceList.tsx +29 -16
- package/src/agent-instance/__tests__/agent-instance-hooks.test.tsx +19 -0
- package/src/agent-instance/useAgentInstances.ts +12 -3
- package/src/button/Button.tsx +104 -0
- package/src/button/__tests__/Button.test.tsx +82 -0
- package/src/button/index.ts +2 -0
- package/src/empty-state/EmptyState.tsx +8 -10
- package/src/empty-state/types.ts +2 -0
- package/src/execution/__tests__/useCreateAgentExecution.test.tsx +92 -0
- package/src/execution/index.ts +2 -0
- package/src/execution/useCreateAgentExecution.ts +116 -10
- package/src/index.ts +17 -9
- package/src/session/__tests__/useNewSessionFlow.test.tsx +121 -52
- package/src/session/useNewSessionFlow.ts +38 -37
- package/src/sharing/AgentShareList.tsx +502 -0
- package/src/sharing/ShareAgentDialog.tsx +310 -112
- package/src/sharing/__tests__/AgentShareList.test.tsx +388 -0
- package/src/sharing/__tests__/ShareAgentDialog.test.tsx +311 -312
- package/src/sharing/__tests__/useAgentShares.test.tsx +186 -0
- package/src/sharing/__tests__/useCanCreateAgentShare.test.tsx +190 -0
- package/src/sharing/index.ts +10 -12
- package/src/sharing/useAgentShares.ts +74 -0
- package/src/sharing/useCanCreateAgentShare.ts +74 -0
- package/src/sharing/useDeleteAgentShare.ts +73 -0
- package/src/sharing/useSaveAgentShare.ts +73 -23
- package/src/workflow/WorkflowDetailView.tsx +9 -0
- package/src/workflow/instance/WorkflowInstanceEmptyState.tsx +24 -43
- package/src/workflow/instance/WorkflowInstanceList.tsx +29 -16
- package/src/workflow/useWorkflowInstances.ts +10 -3
- package/styles.css +1 -1
- package/workflow/WorkflowDetailView.d.ts +8 -1
- package/workflow/WorkflowDetailView.d.ts.map +1 -1
- package/workflow/WorkflowDetailView.js +2 -2
- package/workflow/WorkflowDetailView.js.map +1 -1
- package/workflow/instance/WorkflowInstanceEmptyState.d.ts +4 -1
- package/workflow/instance/WorkflowInstanceEmptyState.d.ts.map +1 -1
- package/workflow/instance/WorkflowInstanceEmptyState.js +12 -5
- package/workflow/instance/WorkflowInstanceEmptyState.js.map +1 -1
- package/workflow/instance/WorkflowInstanceList.d.ts +13 -2
- package/workflow/instance/WorkflowInstanceList.d.ts.map +1 -1
- package/workflow/instance/WorkflowInstanceList.js +8 -4
- package/workflow/instance/WorkflowInstanceList.js.map +1 -1
- package/workflow/useWorkflowInstances.d.ts +8 -3
- package/workflow/useWorkflowInstances.d.ts.map +1 -1
- package/workflow/useWorkflowInstances.js +10 -4
- package/workflow/useWorkflowInstances.js.map +1 -1
- package/sharing/useAgentShare.d.ts +0 -49
- package/sharing/useAgentShare.d.ts.map +0 -1
- package/sharing/useAgentShare.js +0 -64
- package/sharing/useAgentShare.js.map +0 -1
- package/sharing/useCreateExternalShareLink.d.ts +0 -90
- package/sharing/useCreateExternalShareLink.d.ts.map +0 -1
- package/sharing/useCreateExternalShareLink.js +0 -65
- package/sharing/useCreateExternalShareLink.js.map +0 -1
- package/sharing/useShareAgent.d.ts +0 -69
- package/sharing/useShareAgent.d.ts.map +0 -1
- package/sharing/useShareAgent.js +0 -42
- package/sharing/useShareAgent.js.map +0 -1
- package/src/sharing/__tests__/useAgentShare.test.tsx +0 -210
- package/src/sharing/__tests__/useCreateExternalShareLink.test.tsx +0 -194
- package/src/sharing/__tests__/useShareAgent.test.tsx +0 -131
- package/src/sharing/useAgentShare.ts +0 -105
- package/src/sharing/useCreateExternalShareLink.tsx +0 -141
- package/src/sharing/useShareAgent.tsx +0 -107
|
@@ -7,16 +7,9 @@ import type { ModelRegistryState } from "../../models/ModelRegistryContext";
|
|
|
7
7
|
import { ExecutionTargetContext } from "../../execution-target-context";
|
|
8
8
|
import { RunnerAdapterContext } from "../../runner-adapter";
|
|
9
9
|
import type { RunnerAdapter } from "../../runner-adapter";
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
vi.mock("../useCreateSession", () => ({
|
|
14
|
-
useCreateSession: () => ({
|
|
15
|
-
create: mockCreateSession,
|
|
16
|
-
isCreating: false,
|
|
17
|
-
error: null,
|
|
18
|
-
clearError: vi.fn(),
|
|
19
|
-
}),
|
|
10
|
+
const mockGetByReference = vi.fn();
|
|
11
|
+
vi.mock("../../hooks", () => ({
|
|
12
|
+
useStigmer: () => ({ agent: { getByReference: mockGetByReference } }),
|
|
20
13
|
}));
|
|
21
14
|
|
|
22
15
|
const mockCreateExecution = vi.fn();
|
|
@@ -127,8 +120,10 @@ describe("useNewSessionFlow", () => {
|
|
|
127
120
|
};
|
|
128
121
|
mockDefaultAgent.isLoading = false;
|
|
129
122
|
mockDefaultAgent.error = null;
|
|
130
|
-
|
|
131
|
-
|
|
123
|
+
mockCreateExecution.mockResolvedValue({
|
|
124
|
+
executionId: "exec-new",
|
|
125
|
+
sessionId: "sess-new",
|
|
126
|
+
});
|
|
132
127
|
});
|
|
133
128
|
|
|
134
129
|
afterEach(() => {
|
|
@@ -296,8 +291,8 @@ describe("useNewSessionFlow", () => {
|
|
|
296
291
|
});
|
|
297
292
|
});
|
|
298
293
|
|
|
299
|
-
describe("submit
|
|
300
|
-
it("
|
|
294
|
+
describe("submit — one-call bootstrap", () => {
|
|
295
|
+
it("creates the execution with an embedded sessionSpec in a single call", async () => {
|
|
301
296
|
const opts = defaultOptions();
|
|
302
297
|
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: createWrapper() });
|
|
303
298
|
|
|
@@ -305,12 +300,27 @@ describe("useNewSessionFlow", () => {
|
|
|
305
300
|
await result.current.submit("Hello");
|
|
306
301
|
});
|
|
307
302
|
|
|
308
|
-
expect(
|
|
309
|
-
const
|
|
310
|
-
expect(
|
|
303
|
+
expect(mockCreateExecution).toHaveBeenCalledOnce();
|
|
304
|
+
const execInput = mockCreateExecution.mock.calls[0][0];
|
|
305
|
+
expect(execInput.message).toBe("Hello");
|
|
306
|
+
expect(execInput.sessionSpec).toBeDefined();
|
|
307
|
+
expect(execInput.sessionSpec.agentInstanceId).toBe("default-inst");
|
|
308
|
+
expect(execInput.sessionId).toBeUndefined();
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it("passes harness in the sessionSpec", async () => {
|
|
312
|
+
const opts = defaultOptions();
|
|
313
|
+
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: createWrapper() });
|
|
314
|
+
|
|
315
|
+
await act(async () => {
|
|
316
|
+
await result.current.submit("Hello");
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
const execInput = mockCreateExecution.mock.calls[0][0];
|
|
320
|
+
expect(execInput.sessionSpec.harness).toBe("native");
|
|
311
321
|
});
|
|
312
322
|
|
|
313
|
-
it("passes cursor harness
|
|
323
|
+
it("passes cursor harness in the sessionSpec after switching", async () => {
|
|
314
324
|
const opts = defaultOptions();
|
|
315
325
|
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: createWrapper() });
|
|
316
326
|
|
|
@@ -320,11 +330,11 @@ describe("useNewSessionFlow", () => {
|
|
|
320
330
|
await result.current.submit("Hello");
|
|
321
331
|
});
|
|
322
332
|
|
|
323
|
-
const
|
|
324
|
-
expect(
|
|
333
|
+
const execInput = mockCreateExecution.mock.calls[0][0];
|
|
334
|
+
expect(execInput.sessionSpec.harness).toBe("cursor");
|
|
325
335
|
});
|
|
326
336
|
|
|
327
|
-
it("calls onSessionCreated
|
|
337
|
+
it("calls onSessionCreated with the server-assigned session id", async () => {
|
|
328
338
|
const opts = defaultOptions();
|
|
329
339
|
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: createWrapper() });
|
|
330
340
|
|
|
@@ -336,7 +346,7 @@ describe("useNewSessionFlow", () => {
|
|
|
336
346
|
});
|
|
337
347
|
|
|
338
348
|
it("sets submitError and calls onError on failure", async () => {
|
|
339
|
-
|
|
349
|
+
mockCreateExecution.mockRejectedValueOnce(new Error("RPC fail"));
|
|
340
350
|
const opts = defaultOptions();
|
|
341
351
|
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: createWrapper() });
|
|
342
352
|
|
|
@@ -360,8 +370,48 @@ describe("useNewSessionFlow", () => {
|
|
|
360
370
|
});
|
|
361
371
|
});
|
|
362
372
|
|
|
373
|
+
describe("submit — agent resolution strategies", () => {
|
|
374
|
+
it("uses the saved resolution's instance directly (no agent lookup)", async () => {
|
|
375
|
+
const opts = defaultOptions();
|
|
376
|
+
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: createWrapper() });
|
|
377
|
+
|
|
378
|
+
act(() => {
|
|
379
|
+
result.current.setAgentRef({ org: "acme", slug: "reviewer" });
|
|
380
|
+
result.current.setResolution({ mode: "saved", instanceId: "saved-inst" });
|
|
381
|
+
});
|
|
382
|
+
await act(async () => {
|
|
383
|
+
await result.current.submit("Hello");
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
expect(mockGetByReference).not.toHaveBeenCalled();
|
|
387
|
+
const execInput = mockCreateExecution.mock.calls[0][0];
|
|
388
|
+
expect(execInput.sessionSpec.agentInstanceId).toBe("saved-inst");
|
|
389
|
+
expect(execInput.agentId).toBeUndefined();
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it("resolves a non-saved agentRef to agentId and lets the server pick the instance", async () => {
|
|
393
|
+
mockGetByReference.mockResolvedValueOnce({ metadata: { id: "agt-resolved" } });
|
|
394
|
+
const opts = defaultOptions();
|
|
395
|
+
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: createWrapper() });
|
|
396
|
+
|
|
397
|
+
act(() => {
|
|
398
|
+
result.current.setAgentRef({ org: "acme", slug: "reviewer" });
|
|
399
|
+
result.current.setResolution({ mode: "direct" });
|
|
400
|
+
});
|
|
401
|
+
await act(async () => {
|
|
402
|
+
await result.current.submit("Hello");
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
expect(mockGetByReference).toHaveBeenCalledWith({ org: "acme", slug: "reviewer" });
|
|
406
|
+
const execInput = mockCreateExecution.mock.calls[0][0];
|
|
407
|
+
expect(execInput.agentId).toBe("agt-resolved");
|
|
408
|
+
// The server resolves (and if needed creates) the default instance.
|
|
409
|
+
expect(execInput.sessionSpec.agentInstanceId).toBeUndefined();
|
|
410
|
+
});
|
|
411
|
+
});
|
|
412
|
+
|
|
363
413
|
describe("submit with executionTarget", () => {
|
|
364
|
-
it("passes executionTarget
|
|
414
|
+
it("passes executionTarget in the sessionSpec when provided", async () => {
|
|
365
415
|
const opts = { ...defaultOptions(), executionTarget: "local" as const };
|
|
366
416
|
const { result } = renderHook(() => useNewSessionFlow(opts), { wrapper: createWrapper() });
|
|
367
417
|
|
|
@@ -369,9 +419,9 @@ describe("useNewSessionFlow", () => {
|
|
|
369
419
|
await result.current.submit("Hello");
|
|
370
420
|
});
|
|
371
421
|
|
|
372
|
-
expect(
|
|
373
|
-
const
|
|
374
|
-
expect(
|
|
422
|
+
expect(mockCreateExecution).toHaveBeenCalledOnce();
|
|
423
|
+
const execInput = mockCreateExecution.mock.calls[0][0];
|
|
424
|
+
expect(execInput.sessionSpec.executionTarget).toBe("local");
|
|
375
425
|
});
|
|
376
426
|
|
|
377
427
|
it("does not include executionTarget when not provided", async () => {
|
|
@@ -382,9 +432,9 @@ describe("useNewSessionFlow", () => {
|
|
|
382
432
|
await result.current.submit("Hello");
|
|
383
433
|
});
|
|
384
434
|
|
|
385
|
-
expect(
|
|
386
|
-
const
|
|
387
|
-
expect(
|
|
435
|
+
expect(mockCreateExecution).toHaveBeenCalledOnce();
|
|
436
|
+
const execInput = mockCreateExecution.mock.calls[0][0];
|
|
437
|
+
expect(execInput.sessionSpec.executionTarget).toBeUndefined();
|
|
388
438
|
});
|
|
389
439
|
|
|
390
440
|
it("uses context executionTarget when per-hook option is omitted", async () => {
|
|
@@ -395,9 +445,9 @@ describe("useNewSessionFlow", () => {
|
|
|
395
445
|
await result.current.submit("Hello");
|
|
396
446
|
});
|
|
397
447
|
|
|
398
|
-
expect(
|
|
399
|
-
const
|
|
400
|
-
expect(
|
|
448
|
+
expect(mockCreateExecution).toHaveBeenCalledOnce();
|
|
449
|
+
const execInput = mockCreateExecution.mock.calls[0][0];
|
|
450
|
+
expect(execInput.sessionSpec.executionTarget).toBe("local");
|
|
401
451
|
});
|
|
402
452
|
|
|
403
453
|
it("per-hook executionTarget option overrides context", async () => {
|
|
@@ -408,14 +458,14 @@ describe("useNewSessionFlow", () => {
|
|
|
408
458
|
await result.current.submit("Hello");
|
|
409
459
|
});
|
|
410
460
|
|
|
411
|
-
expect(
|
|
412
|
-
const
|
|
413
|
-
expect(
|
|
461
|
+
expect(mockCreateExecution).toHaveBeenCalledOnce();
|
|
462
|
+
const execInput = mockCreateExecution.mock.calls[0][0];
|
|
463
|
+
expect(execInput.sessionSpec.executionTarget).toBe("local");
|
|
414
464
|
});
|
|
415
465
|
});
|
|
416
466
|
|
|
417
|
-
describe("submit — local runner worker (
|
|
418
|
-
it("attaches the worker
|
|
467
|
+
describe("submit — local runner worker (post-create attach)", () => {
|
|
468
|
+
it("attaches the worker after the bootstrap create returns the session id", async () => {
|
|
419
469
|
const adapter = createMockAdapter();
|
|
420
470
|
const opts = defaultOptions();
|
|
421
471
|
const { result } = renderHook(() => useNewSessionFlow(opts), {
|
|
@@ -430,10 +480,12 @@ describe("useNewSessionFlow", () => {
|
|
|
430
480
|
expect(adapter.onSessionOpened).toHaveBeenCalledWith("sess-new");
|
|
431
481
|
expect(adapter.onSessionClosed).not.toHaveBeenCalled();
|
|
432
482
|
|
|
433
|
-
// The
|
|
483
|
+
// The session ID only exists after the one-call create, so the attach
|
|
484
|
+
// follows it. This is safe: the first activity waits on the session's
|
|
485
|
+
// task queue for a worker (5-minute ScheduleToStart window).
|
|
434
486
|
const openedOrder = adapter.onSessionOpened.mock.invocationCallOrder[0];
|
|
435
487
|
const execOrder = mockCreateExecution.mock.invocationCallOrder[0];
|
|
436
|
-
expect(
|
|
488
|
+
expect(execOrder).toBeLessThan(openedOrder);
|
|
437
489
|
});
|
|
438
490
|
|
|
439
491
|
it("does not attach a worker when the target is cloud", async () => {
|
|
@@ -450,7 +502,7 @@ describe("useNewSessionFlow", () => {
|
|
|
450
502
|
expect(adapter.onSessionOpened).not.toHaveBeenCalled();
|
|
451
503
|
});
|
|
452
504
|
|
|
453
|
-
it("
|
|
505
|
+
it("never attaches a worker when the bootstrap create fails (no leak)", async () => {
|
|
454
506
|
const adapter = createMockAdapter();
|
|
455
507
|
mockCreateExecution.mockRejectedValueOnce(new Error("execution boom"));
|
|
456
508
|
const opts = defaultOptions();
|
|
@@ -462,11 +514,30 @@ describe("useNewSessionFlow", () => {
|
|
|
462
514
|
await result.current.submit("Hello");
|
|
463
515
|
});
|
|
464
516
|
|
|
465
|
-
|
|
466
|
-
|
|
517
|
+
// The create failed before a session existed, so there is no worker
|
|
518
|
+
// to attach — and therefore nothing to compensate/detach.
|
|
519
|
+
expect(adapter.onSessionOpened).not.toHaveBeenCalled();
|
|
520
|
+
expect(adapter.onSessionClosed).not.toHaveBeenCalled();
|
|
467
521
|
expect(result.current.submitError).not.toBeNull();
|
|
468
522
|
expect(opts.onError).toHaveBeenCalled();
|
|
469
523
|
});
|
|
524
|
+
|
|
525
|
+
it("surfaces an attach failure without navigating", async () => {
|
|
526
|
+
const adapter = createMockAdapter();
|
|
527
|
+
adapter.onSessionOpened.mockRejectedValueOnce(new Error("runner down"));
|
|
528
|
+
const opts = defaultOptions();
|
|
529
|
+
const { result } = renderHook(() => useNewSessionFlow(opts), {
|
|
530
|
+
wrapper: createWrapper("local", adapter),
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
await act(async () => {
|
|
534
|
+
await result.current.submit("Hello");
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
expect(result.current.submitError).not.toBeNull();
|
|
538
|
+
expect(opts.onError).toHaveBeenCalled();
|
|
539
|
+
expect(opts.onSessionCreated).not.toHaveBeenCalled();
|
|
540
|
+
});
|
|
470
541
|
});
|
|
471
542
|
|
|
472
543
|
describe("host runtime env (getRuntimeEnv)", () => {
|
|
@@ -528,7 +599,6 @@ describe("useNewSessionFlow", () => {
|
|
|
528
599
|
});
|
|
529
600
|
|
|
530
601
|
// No orphan session, no execution — the failure is fully pre-flight.
|
|
531
|
-
expect(mockCreateSession).not.toHaveBeenCalled();
|
|
532
602
|
expect(mockCreateExecution).not.toHaveBeenCalled();
|
|
533
603
|
expect(result.current.submitError).toContain("token mint failed");
|
|
534
604
|
expect(opts.onError).toHaveBeenCalled();
|
|
@@ -587,7 +657,7 @@ describe("useNewSessionFlow", () => {
|
|
|
587
657
|
await result.current.submit("Hello");
|
|
588
658
|
});
|
|
589
659
|
|
|
590
|
-
expect(
|
|
660
|
+
expect(mockCreateExecution.mock.calls[0][0].sessionSpec.harness).toBe("cursor");
|
|
591
661
|
});
|
|
592
662
|
});
|
|
593
663
|
|
|
@@ -607,8 +677,8 @@ describe("useNewSessionFlow", () => {
|
|
|
607
677
|
});
|
|
608
678
|
|
|
609
679
|
expect(mockDefaultAgent.waitForResolution).toHaveBeenCalledOnce();
|
|
610
|
-
expect(
|
|
611
|
-
expect(
|
|
680
|
+
expect(mockCreateExecution).toHaveBeenCalledOnce();
|
|
681
|
+
expect(mockCreateExecution.mock.calls[0][0].sessionSpec.agentInstanceId).toBe("awaited-inst");
|
|
612
682
|
expect(opts.onSessionCreated).toHaveBeenCalledWith("sess-new");
|
|
613
683
|
expect(result.current.submitError).toBeNull();
|
|
614
684
|
});
|
|
@@ -630,7 +700,7 @@ describe("useNewSessionFlow", () => {
|
|
|
630
700
|
|
|
631
701
|
expect(result.current.submitError).toBeTruthy();
|
|
632
702
|
expect(opts.onError).toHaveBeenCalled();
|
|
633
|
-
expect(
|
|
703
|
+
expect(mockCreateExecution).not.toHaveBeenCalled();
|
|
634
704
|
});
|
|
635
705
|
|
|
636
706
|
it("surfaces timeout error when fetch never resolves", async () => {
|
|
@@ -661,7 +731,7 @@ describe("useNewSessionFlow", () => {
|
|
|
661
731
|
|
|
662
732
|
expect(result.current.submitError).toContain("did not load in time");
|
|
663
733
|
expect(opts.onError).toHaveBeenCalled();
|
|
664
|
-
expect(
|
|
734
|
+
expect(mockCreateExecution).not.toHaveBeenCalled();
|
|
665
735
|
|
|
666
736
|
vi.useRealTimers();
|
|
667
737
|
});
|
|
@@ -680,7 +750,7 @@ describe("useNewSessionFlow", () => {
|
|
|
680
750
|
|
|
681
751
|
expect(result.current.submitError).toContain("Failed to load default agent");
|
|
682
752
|
expect(opts.onError).toHaveBeenCalled();
|
|
683
|
-
expect(
|
|
753
|
+
expect(mockCreateExecution).not.toHaveBeenCalled();
|
|
684
754
|
expect(mockDefaultAgent.waitForResolution).not.toHaveBeenCalled();
|
|
685
755
|
});
|
|
686
756
|
});
|
|
@@ -711,8 +781,8 @@ describe("useNewSessionFlow", () => {
|
|
|
711
781
|
await result.current.submit("Hello");
|
|
712
782
|
});
|
|
713
783
|
|
|
714
|
-
expect(
|
|
715
|
-
expect(
|
|
784
|
+
expect(mockCreateExecution).toHaveBeenCalledOnce();
|
|
785
|
+
expect(mockCreateExecution.mock.calls[0][0].sessionSpec.agentInstanceId).toBe("shared-inst");
|
|
716
786
|
expect(opts.onSessionCreated).toHaveBeenCalledWith("sess-new");
|
|
717
787
|
});
|
|
718
788
|
|
|
@@ -726,7 +796,6 @@ describe("useNewSessionFlow", () => {
|
|
|
726
796
|
await result.current.submit("Hello");
|
|
727
797
|
});
|
|
728
798
|
|
|
729
|
-
expect(mockCreateSession).not.toHaveBeenCalled();
|
|
730
799
|
expect(mockCreateExecution).not.toHaveBeenCalled();
|
|
731
800
|
expect(result.current.submitError).toContain("still loading");
|
|
732
801
|
expect(opts.onError).toHaveBeenCalled();
|
|
@@ -10,8 +10,8 @@ import { DEFAULT_HARNESS, type HarnessOption } from "../models/harness.js";
|
|
|
10
10
|
import { useWorkspaceEntries, type UseWorkspaceEntriesReturn } from "../workspace/index.js";
|
|
11
11
|
import { useSessionVariables, type UseSessionVariablesReturn } from "../execution/useSessionVariables.js";
|
|
12
12
|
import type { SessionComposerSubmitContext } from "../composer/index.js";
|
|
13
|
+
import { useStigmer } from "../hooks.js";
|
|
13
14
|
import { withTimeout } from "../internal/withTimeout.js";
|
|
14
|
-
import { useCreateSession } from "./useCreateSession.js";
|
|
15
15
|
import { useCreateAgentExecution } from "../execution/useCreateAgentExecution.js";
|
|
16
16
|
import type { ExecutionTargetOption } from "./execution-target.js";
|
|
17
17
|
import { useExecutionTarget } from "../execution-target-context.js";
|
|
@@ -140,8 +140,9 @@ export interface UseNewSessionFlowReturn {
|
|
|
140
140
|
* Create a session with the first execution.
|
|
141
141
|
*
|
|
142
142
|
* Composes all managed state (agent, workspace, MCP servers, skills,
|
|
143
|
-
* model, session variables) into
|
|
144
|
-
*
|
|
143
|
+
* model, session variables) into a single bootstrap RPC —
|
|
144
|
+
* `agentExecution.create` with an embedded session spec — then calls
|
|
145
|
+
* `onSessionCreated` on success.
|
|
145
146
|
*
|
|
146
147
|
* The `model` parameter overrides `modelId` for this submission only
|
|
147
148
|
* (used when SessionComposer passes a per-message model selection).
|
|
@@ -160,7 +161,9 @@ export interface UseNewSessionFlowReturn {
|
|
|
160
161
|
* model selection (with localStorage persistence), agent resolution,
|
|
161
162
|
* MCP server/skill selection, workspace entries, and session
|
|
162
163
|
* variables. On submission, creates the session and its first execution
|
|
163
|
-
*
|
|
164
|
+
* with a single one-call bootstrap RPC (`agentExecution.create` with an
|
|
165
|
+
* embedded session spec), then notifies the consumer via
|
|
166
|
+
* `onSessionCreated`.
|
|
164
167
|
*
|
|
165
168
|
* This hook is framework-agnostic — it works identically in Next.js,
|
|
166
169
|
* Vite, Tauri, or any React environment. Navigation, toast notifications,
|
|
@@ -210,8 +213,8 @@ export function useNewSessionFlow(
|
|
|
210
213
|
return defaultHarness ?? DEFAULT_HARNESS;
|
|
211
214
|
});
|
|
212
215
|
|
|
216
|
+
const stigmer = useStigmer();
|
|
213
217
|
const { getModel, isLoading: isModelsLoading } = useModelRegistry({ harness });
|
|
214
|
-
const { create: createSession } = useCreateSession();
|
|
215
218
|
const { create: createExecution } = useCreateAgentExecution();
|
|
216
219
|
// Guests cannot read the org default agent (and must never run it) —
|
|
217
220
|
// `null` puts the hook in its stable no-op mode, and the submit path
|
|
@@ -296,8 +299,7 @@ export function useNewSessionFlow(
|
|
|
296
299
|
? await resolveExecutionRuntimeEnv(getRuntimeEnv, context?.runtimeEnv)
|
|
297
300
|
: context?.runtimeEnv;
|
|
298
301
|
|
|
299
|
-
const
|
|
300
|
-
org,
|
|
302
|
+
const sessionSpecBase = {
|
|
301
303
|
workspaceEntries: workspace.hasEntries
|
|
302
304
|
? workspace.toInput()
|
|
303
305
|
: undefined,
|
|
@@ -317,19 +319,21 @@ export function useNewSessionFlow(
|
|
|
317
319
|
workspaceFileRefs: context?.workspaceFileRefs,
|
|
318
320
|
};
|
|
319
321
|
|
|
320
|
-
|
|
322
|
+
// Resolve which agent the bootstrapped session runs against: an
|
|
323
|
+
// explicit instance when one is known, otherwise an agent ID the
|
|
324
|
+
// server resolves to its default instance (creating it if missing).
|
|
325
|
+
let agentInstanceId: string | undefined;
|
|
326
|
+
let agentId: string | undefined;
|
|
321
327
|
|
|
322
328
|
if (agentRef && resolution) {
|
|
323
329
|
if (resolution.mode === "saved") {
|
|
324
|
-
|
|
325
|
-
...sessionFields,
|
|
326
|
-
agentInstanceId: resolution.instanceId,
|
|
327
|
-
}));
|
|
330
|
+
agentInstanceId = resolution.instanceId;
|
|
328
331
|
} else {
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
332
|
+
// Slug reference → agent ID; the server handles instance
|
|
333
|
+
// resolution, including auto-creating a missing default
|
|
334
|
+
// instance (which a client-side lookup cannot).
|
|
335
|
+
const agent = await stigmer.agent.getByReference(agentRef);
|
|
336
|
+
agentId = agent.metadata!.id;
|
|
333
337
|
}
|
|
334
338
|
} else if (isGuest) {
|
|
335
339
|
// Fail closed: a guest session is only ever created against the
|
|
@@ -360,32 +364,29 @@ export function useNewSessionFlow(
|
|
|
360
364
|
);
|
|
361
365
|
}
|
|
362
366
|
}
|
|
363
|
-
|
|
364
|
-
...sessionFields,
|
|
365
|
-
agentInstanceId: resolvedInstanceId,
|
|
366
|
-
}));
|
|
367
|
+
agentInstanceId = resolvedInstanceId;
|
|
367
368
|
}
|
|
368
369
|
|
|
369
|
-
//
|
|
370
|
-
//
|
|
371
|
-
//
|
|
372
|
-
|
|
373
|
-
|
|
370
|
+
// One-call bootstrap: the embedded session spec and the first
|
|
371
|
+
// message travel in a single create — the server creates the
|
|
372
|
+
// session and dispatches the execution atomically.
|
|
373
|
+
const { sessionId } = await createExecution({
|
|
374
|
+
...executionFields,
|
|
375
|
+
agentId,
|
|
376
|
+
sessionSpec: { ...sessionSpecBase, agentInstanceId },
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// Local execution: attach the session's runner worker now that the
|
|
380
|
+
// session ID is known. The session view (useSessionConversation)
|
|
381
|
+
// owns the steady-state lifecycle, but it is not mounted yet —
|
|
382
|
+
// navigation happens after onSessionCreated below. Attaching after
|
|
383
|
+
// the create is safe: the first activity waits on the session's
|
|
384
|
+
// task queue for a worker (5-minute ScheduleToStart window), so the
|
|
385
|
+
// worker attached here picks it up immediately.
|
|
374
386
|
if (adapter && executionTarget === "local") {
|
|
375
387
|
await adapter.onSessionOpened(sessionId);
|
|
376
388
|
}
|
|
377
389
|
|
|
378
|
-
try {
|
|
379
|
-
await createExecution({ ...executionFields, sessionId });
|
|
380
|
-
} catch (err) {
|
|
381
|
-
// The first execution failed and the session view will not mount,
|
|
382
|
-
// so detach the worker we just attached to avoid leaking it.
|
|
383
|
-
if (adapter && executionTarget === "local") {
|
|
384
|
-
adapter.onSessionClosed(sessionId).catch(() => {});
|
|
385
|
-
}
|
|
386
|
-
throw err;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
390
|
sessionVariables.clear();
|
|
390
391
|
onSessionCreated(sessionId);
|
|
391
392
|
} catch (err) {
|
|
@@ -414,7 +415,7 @@ export function useNewSessionFlow(
|
|
|
414
415
|
isDefaultAgentLoading,
|
|
415
416
|
defaultAgentError,
|
|
416
417
|
waitForDefaultAgent,
|
|
417
|
-
|
|
418
|
+
stigmer.agent,
|
|
418
419
|
createExecution,
|
|
419
420
|
sessionVariables,
|
|
420
421
|
onSessionCreated,
|