@flowdular/sandbox 0.2.6 → 0.2.8
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/internal/coding-agent/src/drivers/byok.ts +17 -3
- package/internal/coding-agent/src/drivers/claude-code.ts +36 -8
- package/internal/coding-agent/src/drivers/codex.ts +8 -0
- package/internal/coding-agent/src/types.ts +2 -0
- package/internal/coding-agent/src/workspace.ts +21 -5
- package/package.json +1 -1
- package/src/client/ChatPane.tsrx +77 -45
- package/src/client/ComposerSettings.tsrx +99 -0
- package/src/client/ToolEvent.tsrx +77 -0
- package/src/client/TurnActivity.tsrx +26 -0
- package/src/client/locales/en.json +21 -1
- package/src/client/locales/pl.json +21 -1
- package/src/client/transcript.ts +110 -0
- package/src/server/attachments.ts +67 -30
- package/src/server/auto-review.ts +2 -1
- package/src/server/gate-repair.ts +88 -0
- package/src/server/planning.ts +15 -5
- package/src/server/preview-hot-updates.ts +40 -0
- package/src/server/reference.ts +10 -1
- package/src/server/routes.ts +2 -3
- package/src/server/sdk-reference.ts +99 -0
- package/src/server/session-lock.ts +26 -0
- package/src/server/sessions.ts +14 -6
- package/src/server/turns.ts +20 -2
- package/src/styles.css +108 -6
- package/vite.config.ts +5 -1
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
|
|
3
|
+
/* Session metadata and operator-owned attachment snapshots share one queue.
|
|
4
|
+
Failed work releases the next caller and idle sessions retain no lock. */
|
|
5
|
+
const pending = new Map<string, Promise<void>>();
|
|
6
|
+
|
|
7
|
+
export async function withSessionLock<T>(
|
|
8
|
+
workspaceRoot: string,
|
|
9
|
+
sessionId: string,
|
|
10
|
+
work: () => Promise<T>,
|
|
11
|
+
): Promise<T> {
|
|
12
|
+
const key = `${resolve(workspaceRoot)}\0${sessionId}`;
|
|
13
|
+
const previous = pending.get(key);
|
|
14
|
+
let release!: () => void;
|
|
15
|
+
const current = new Promise<void>((done) => {
|
|
16
|
+
release = done;
|
|
17
|
+
});
|
|
18
|
+
pending.set(key, current);
|
|
19
|
+
await previous;
|
|
20
|
+
try {
|
|
21
|
+
return await work();
|
|
22
|
+
} finally {
|
|
23
|
+
release();
|
|
24
|
+
if (pending.get(key) === current) pending.delete(key);
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/server/sessions.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { withSessionLock } from './session-lock.ts';
|
|
1
2
|
import { randomUUID } from 'node:crypto';
|
|
2
3
|
import {
|
|
3
4
|
access,
|
|
@@ -588,16 +589,23 @@ export async function listSessions(
|
|
|
588
589
|
return sessions.sort((left, right) => right.updatedAt - left.updatedAt);
|
|
589
590
|
}
|
|
590
591
|
|
|
592
|
+
type SessionPatch = Partial<Omit<SandboxSession, 'id' | 'createdAt'>>;
|
|
593
|
+
|
|
591
594
|
export async function updateSession(
|
|
592
595
|
workspaceRoot: string,
|
|
593
596
|
sessionId: string,
|
|
594
|
-
patch:
|
|
597
|
+
patch:
|
|
598
|
+
| SessionPatch
|
|
599
|
+
| ((current: SandboxSession) => SessionPatch | Promise<SessionPatch>),
|
|
595
600
|
): Promise<SandboxSession> {
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
+
return withSessionLock(workspaceRoot, sessionId, async () => {
|
|
602
|
+
const current = await readSession(workspaceRoot, sessionId);
|
|
603
|
+
const updates = typeof patch === 'function' ? await patch(current) : patch;
|
|
604
|
+
return writeSession(workspaceRoot, {
|
|
605
|
+
...current,
|
|
606
|
+
...updates,
|
|
607
|
+
updatedAt: Date.now(),
|
|
608
|
+
});
|
|
601
609
|
});
|
|
602
610
|
}
|
|
603
611
|
|
package/src/server/turns.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { materializeSdkReference } from './sdk-reference.ts';
|
|
1
2
|
import { spawn } from 'node:child_process';
|
|
2
3
|
import { createHash } from 'node:crypto';
|
|
3
4
|
import {
|
|
@@ -28,7 +29,10 @@ import {
|
|
|
28
29
|
moduleReviewRevision,
|
|
29
30
|
recordAutoReview,
|
|
30
31
|
} from './auto-review.ts';
|
|
31
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
attachmentInstruction,
|
|
34
|
+
materializeAttachments,
|
|
35
|
+
} from './attachments.ts';
|
|
32
36
|
import { captureCheckpoint } from './checkpoints.ts';
|
|
33
37
|
import { guardAgentPaths } from './path-guard.ts';
|
|
34
38
|
import type { SandboxConfiguration } from './config.ts';
|
|
@@ -535,7 +539,7 @@ export async function* runTurn(
|
|
|
535
539
|
context: TurnContext,
|
|
536
540
|
input: TurnInput,
|
|
537
541
|
): AsyncGenerator<ChatEntry, TurnOutcome> {
|
|
538
|
-
|
|
542
|
+
let session = await readSession(context.workspaceRoot, input.sessionId);
|
|
539
543
|
const paths = sessionPaths(
|
|
540
544
|
context.workspaceRoot,
|
|
541
545
|
session.id,
|
|
@@ -576,6 +580,14 @@ export async function* runTurn(
|
|
|
576
580
|
const driverId = input.driver ?? session.driver;
|
|
577
581
|
const driver = await context.registry.resolve(driverId);
|
|
578
582
|
|
|
583
|
+
session = {
|
|
584
|
+
...session,
|
|
585
|
+
attachments: await materializeAttachments(context.workspaceRoot, session),
|
|
586
|
+
};
|
|
587
|
+
const hasSdk = await materializeSdkReference(
|
|
588
|
+
context.workspaceRoot,
|
|
589
|
+
paths.workspace,
|
|
590
|
+
);
|
|
579
591
|
const attachmentNote = attachmentInstruction(session.attachments);
|
|
580
592
|
|
|
581
593
|
yield await appendChatEntry(context.workspaceRoot, session, {
|
|
@@ -671,6 +683,11 @@ export async function* runTurn(
|
|
|
671
683
|
)}. This turn is yours in modules/${active.directory} only; another specialist takes the turn for the others. Each module is a project of this pnpm workspace, so a draft that imports another draft resolves the session copy.`,
|
|
672
684
|
]
|
|
673
685
|
: []),
|
|
686
|
+
...(hasSdk
|
|
687
|
+
? [
|
|
688
|
+
'Read the actual installed SDK under reference/sdk/packages and reference/sdk/modules. Its package.json maps public exports. These are readable copies inside the workspace; do not follow external SDK symlinks. Search only the API needed for the current task.',
|
|
689
|
+
]
|
|
690
|
+
: []),
|
|
674
691
|
'reference/ is read-only. Consult only the code and references needed for this task; do not preload its catalog.',
|
|
675
692
|
'Other module.json files under modules/ describe the dependency graph. Only the draft module directories have sources you may change.',
|
|
676
693
|
...(active.kind === 'edit'
|
|
@@ -685,6 +702,7 @@ export async function* runTurn(
|
|
|
685
702
|
driverId === 'claude-code' ? 'CLAUDE.md' : 'AGENTS.md',
|
|
686
703
|
role.name,
|
|
687
704
|
skill,
|
|
705
|
+
hasSdk,
|
|
688
706
|
);
|
|
689
707
|
|
|
690
708
|
const history = historyFrom(await readChat(context.workspaceRoot, session));
|
package/src/styles.css
CHANGED
|
@@ -683,13 +683,16 @@
|
|
|
683
683
|
color: var(--danger);
|
|
684
684
|
}
|
|
685
685
|
|
|
686
|
-
.
|
|
686
|
+
.chat-settings {
|
|
687
687
|
display: inline-flex;
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
.chat-settings__panel {
|
|
691
|
+
position: fixed;
|
|
692
|
+
z-index: 20;
|
|
693
|
+
width: min(320px, calc(100vw - 32px));
|
|
694
|
+
max-height: calc(100dvh - 32px);
|
|
695
|
+
overflow-y: auto;
|
|
693
696
|
}
|
|
694
697
|
|
|
695
698
|
.chat__selection {
|
|
@@ -1571,3 +1574,102 @@
|
|
|
1571
1574
|
font-size: var(--text-md);
|
|
1572
1575
|
color: var(--ink-3);
|
|
1573
1576
|
}
|
|
1577
|
+
|
|
1578
|
+
@keyframes chat-activity-spin {
|
|
1579
|
+
to {
|
|
1580
|
+
transform: rotate(360deg);
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
.chat__event--active > svg {
|
|
1584
|
+
animation: chat-activity-spin 1.5s linear infinite;
|
|
1585
|
+
}
|
|
1586
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1587
|
+
.chat__event--active > svg {
|
|
1588
|
+
animation: none;
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
/* Compact operation summaries with the original details available on demand. */
|
|
1593
|
+
.chat-tool {
|
|
1594
|
+
min-width: 0;
|
|
1595
|
+
color: var(--ink-2);
|
|
1596
|
+
font-size: var(--text-sm);
|
|
1597
|
+
}
|
|
1598
|
+
.chat-tool__summary {
|
|
1599
|
+
display: flex;
|
|
1600
|
+
align-items: center;
|
|
1601
|
+
gap: 8px;
|
|
1602
|
+
min-width: 0;
|
|
1603
|
+
padding: 6px 0;
|
|
1604
|
+
cursor: pointer;
|
|
1605
|
+
list-style: none;
|
|
1606
|
+
}
|
|
1607
|
+
.chat-tool__summary::-webkit-details-marker {
|
|
1608
|
+
display: none;
|
|
1609
|
+
}
|
|
1610
|
+
.chat-tool__summary:focus-visible {
|
|
1611
|
+
outline: 2px solid var(--focus);
|
|
1612
|
+
outline-offset: 2px;
|
|
1613
|
+
}
|
|
1614
|
+
.chat-tool__summary > svg {
|
|
1615
|
+
flex-shrink: 0;
|
|
1616
|
+
}
|
|
1617
|
+
.chat-tool__action {
|
|
1618
|
+
flex-shrink: 0;
|
|
1619
|
+
}
|
|
1620
|
+
.chat-tool__target {
|
|
1621
|
+
min-width: 0;
|
|
1622
|
+
flex: 1;
|
|
1623
|
+
overflow: hidden;
|
|
1624
|
+
text-overflow: ellipsis;
|
|
1625
|
+
white-space: nowrap;
|
|
1626
|
+
font-family: var(--font-mono);
|
|
1627
|
+
}
|
|
1628
|
+
.chat-tool__status,
|
|
1629
|
+
.chat-tool__duration {
|
|
1630
|
+
color: var(--ink-3);
|
|
1631
|
+
flex-shrink: 0;
|
|
1632
|
+
font-variant-numeric: tabular-nums;
|
|
1633
|
+
}
|
|
1634
|
+
.chat-tool--failed .chat-tool__status,
|
|
1635
|
+
.chat-tool--failed .chat-tool__summary > svg:first-child {
|
|
1636
|
+
color: var(--danger);
|
|
1637
|
+
}
|
|
1638
|
+
.chat-tool[open] .chat-tool__summary > svg:last-child {
|
|
1639
|
+
transform: rotate(180deg);
|
|
1640
|
+
}
|
|
1641
|
+
.chat-tool__details {
|
|
1642
|
+
margin: 0 0 8px 22px;
|
|
1643
|
+
padding: 12px;
|
|
1644
|
+
border: 1px solid var(--line);
|
|
1645
|
+
border-radius: var(--r-sm);
|
|
1646
|
+
}
|
|
1647
|
+
.chat-tool__details dt {
|
|
1648
|
+
color: var(--ink-3);
|
|
1649
|
+
margin-bottom: 4px;
|
|
1650
|
+
}
|
|
1651
|
+
.chat-tool__details dd {
|
|
1652
|
+
margin: 0 0 8px;
|
|
1653
|
+
white-space: pre-wrap;
|
|
1654
|
+
overflow-wrap: anywhere;
|
|
1655
|
+
font-family: var(--font-mono);
|
|
1656
|
+
}
|
|
1657
|
+
.chat-tool__details dd:last-child {
|
|
1658
|
+
margin-bottom: 0;
|
|
1659
|
+
}
|
|
1660
|
+
.chat-tool--running .chat-tool__summary > svg:first-child {
|
|
1661
|
+
animation: chat-activity-spin 1.5s linear infinite;
|
|
1662
|
+
}
|
|
1663
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1664
|
+
.chat-tool--running .chat-tool__summary > svg:first-child {
|
|
1665
|
+
animation: none;
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
@media (max-width: 600px) {
|
|
1669
|
+
.chat-tool__summary {
|
|
1670
|
+
flex-wrap: wrap;
|
|
1671
|
+
}
|
|
1672
|
+
.chat-tool__target {
|
|
1673
|
+
flex-basis: calc(100% - 32px);
|
|
1674
|
+
}
|
|
1675
|
+
}
|
package/vite.config.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { defineConfig } from 'vite';
|
|
|
9
9
|
import { sandboxDirectory } from './src/server/config.ts';
|
|
10
10
|
import { findFlowdularWorkspace } from './src/server/workspace-root.ts';
|
|
11
11
|
import { resolvePreviewModules } from './src/server/preview-modules.ts';
|
|
12
|
+
import { isolatePreviewHotUpdates } from './src/server/preview-hot-updates.ts';
|
|
12
13
|
import type { SandboxSession } from './src/server/sessions.ts';
|
|
13
14
|
|
|
14
15
|
Object.assign(process.env, flowdularEnvironment(process.env));
|
|
@@ -103,7 +104,10 @@ function previewModules(workspaceRoot: string): Plugin {
|
|
|
103
104
|
|
|
104
105
|
const config = {
|
|
105
106
|
root: appRoot,
|
|
106
|
-
plugins: [
|
|
107
|
+
plugins: [
|
|
108
|
+
previewModules(workspace.root),
|
|
109
|
+
...isolatePreviewHotUpdates(octane(), workspace.root),
|
|
110
|
+
],
|
|
107
111
|
resolve: {
|
|
108
112
|
/* Draft module code is loaded from a session workspace outside this app.
|
|
109
113
|
It resolves the workspace packages through the node_modules link the
|