@astrale-os/cli 1.0.0-beta.0 → 1.0.0-beta.10
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/README.md +23 -12
- package/dist/astrale.js +51494 -56307
- package/dist/public/connect-core.js +2028 -26846
- package/dist/public/keys/index.js +1755 -40694
- package/dist/public/paths/index.js +859 -39518
- package/dist/types/admin/binding.d.ts +19 -0
- package/dist/types/admin/instance/client.d.ts +22 -0
- package/dist/types/admin/instance/index.d.ts +2 -0
- package/dist/types/admin/instance/model.d.ts +5 -12
- package/dist/types/connection/auth.d.ts +3 -0
- package/dist/types/connection/call.d.ts +3 -0
- package/dist/types/connection/command.d.ts +27 -0
- package/dist/types/connection/credential.d.ts +14 -0
- package/dist/types/connection/errors.d.ts +1 -0
- package/dist/types/connection/exchange.d.ts +9 -0
- package/dist/types/connection/failure/classify.d.ts +2 -0
- package/dist/types/connection/failure/index.d.ts +5 -0
- package/dist/types/connection/failure/model.d.ts +25 -0
- package/dist/types/connection/failure/render.d.ts +3 -0
- package/dist/types/connection/index.d.ts +8 -0
- package/dist/types/connection/reasons.d.ts +37 -0
- package/dist/types/connection/self.d.ts +37 -0
- package/dist/types/connection/session.d.ts +31 -0
- package/dist/types/connection/target.d.ts +38 -0
- package/dist/types/errors.d.ts +1 -1
- package/dist/types/lib/admin-instance.d.ts +17 -0
- package/dist/types/lib/admin-target.d.ts +6 -2
- package/dist/types/lib/failure-debug.d.ts +1 -0
- package/dist/types/lib/idp.d.ts +7 -0
- package/dist/types/lib/instance-target.d.ts +1 -1
- package/dist/types/lib/instance.d.ts +10 -2
- package/dist/types/lib/log.d.ts +5 -3
- package/dist/types/lib/meta.d.ts +2 -2
- package/dist/types/lib/proc.d.ts +43 -0
- package/dist/types/lib/self.d.ts +9 -0
- package/dist/types/lib/update.d.ts +131 -0
- package/package.json +8 -10
- package/src/__tests__/fixtures/publication.ts +20 -0
- package/src/admin/__tests__/binding.test.ts +31 -0
- package/src/admin/__tests__/fixture.ts +101 -0
- package/src/admin/binding.ts +98 -0
- package/src/admin/catalog/.spec/api.d.ts +2 -2
- package/src/admin/catalog/__tests__/client.test.ts +20 -49
- package/src/admin/catalog/client.ts +36 -32
- package/src/admin/catalog/model.ts +3 -4
- package/src/admin/graph/.spec/api.d.ts +4 -10
- package/src/admin/graph/nodes.ts +1 -1
- package/src/admin/instance/.spec/api.d.ts +2 -2
- package/src/admin/instance/__tests__/client.test.ts +40 -163
- package/src/admin/instance/client.ts +35 -137
- package/src/admin/instance/index.ts +1 -2
- package/src/admin/instance/model.ts +11 -20
- package/src/commands/__tests__/admin-instance.test.ts +11 -8
- package/src/commands/__tests__/auth-login.test.ts +54 -1
- package/src/commands/__tests__/call-describe.test.ts +46 -33
- package/src/commands/__tests__/call.test.ts +5 -1
- package/src/commands/__tests__/domain-install-operation.test.ts +12 -7
- package/src/commands/__tests__/domain-install-owned.test.ts +6 -2
- package/src/commands/__tests__/domain-list.test.ts +63 -4
- package/src/commands/__tests__/install-identity-override.test.ts +19 -19
- package/src/commands/__tests__/instance-list-rows.test.ts +1 -1
- package/src/commands/__tests__/instance-status.test.ts +139 -0
- package/src/commands/__tests__/read-commands.test.ts +35 -10
- package/src/commands/__tests__/studio-descriptor.test.ts +22 -0
- package/src/commands/__tests__/update.test.ts +95 -0
- package/src/commands/call-describe.ts +60 -68
- package/src/commands/call.ts +43 -56
- package/src/commands/domain/install.ts +13 -17
- package/src/commands/domain/list.ts +18 -13
- package/src/commands/domain/publish.ts +14 -11
- package/src/commands/domain/uninstall.ts +2 -2
- package/src/commands/get.ts +14 -15
- package/src/commands/identity/__tests__/register.test.ts +3 -3
- package/src/commands/identity/register.ts +5 -4
- package/src/commands/instance/create.ts +8 -16
- package/src/commands/instance/delete.ts +12 -9
- package/src/commands/instance/list.ts +2 -2
- package/src/commands/instance/status.ts +66 -20
- package/src/commands/instance/use.ts +16 -12
- package/src/commands/introspect.ts +9 -16
- package/src/commands/logs.ts +24 -25
- package/src/commands/mutate.ts +6 -5
- package/src/commands/query.ts +27 -30
- package/src/commands/studio.ts +20 -0
- package/src/commands/token.ts +8 -7
- package/src/commands/update.ts +68 -22
- package/src/commands/view.ts +7 -6
- package/src/connection/.spec/api.d.ts +7 -8
- package/src/connection/.spec/architecture.md +5 -4
- package/src/connection/.spec/flows/session.ts +2 -2
- package/src/connection/.spec/laws/connection.ts +6 -2
- package/src/connection/.spec/layout.ts +1 -0
- package/src/connection/__tests__/credential.test.ts +20 -1
- package/src/connection/__tests__/errors.test.ts +43 -32
- package/src/connection/__tests__/exchange.test.ts +36 -6
- package/src/connection/__tests__/failure-fixtures.ts +39 -0
- package/src/connection/__tests__/failure-safety.test.ts +75 -0
- package/src/connection/__tests__/self.test.ts +31 -11
- package/src/connection/__tests__/session.test.ts +3 -3
- package/src/connection/__tests__/target.test.ts +1 -0
- package/src/connection/auth.ts +13 -0
- package/src/connection/call.ts +1 -1
- package/src/connection/credential.ts +2 -2
- package/src/connection/errors.ts +1 -355
- package/src/connection/exchange.ts +52 -27
- package/src/connection/failure/classify.ts +110 -0
- package/src/connection/failure/index.ts +18 -0
- package/src/connection/failure/model.ts +28 -0
- package/src/connection/failure/render.ts +116 -0
- package/src/connection/reasons.ts +16 -0
- package/src/connection/self.ts +39 -21
- package/src/connection/session.ts +19 -8
- package/src/errors.ts +3 -2
- package/src/graph/.spec/api.d.ts +12 -5
- package/src/graph/.spec/layout.ts +9 -1
- package/src/graph/__tests__/mutation.test.ts +2 -2
- package/src/graph/__tests__/query.test.ts +25 -6
- package/src/graph/class.ts +30 -0
- package/src/graph/index.ts +1 -0
- package/src/graph/query.ts +21 -32
- package/src/identity/.spec/api.d.ts +2 -2
- package/src/identity/__tests__/registration.test.ts +1 -1
- package/src/identity/registration.ts +3 -3
- package/src/keys/__tests__/keys.test.ts +4 -0
- package/src/keys/credential.ts +1 -0
- package/src/lib/__tests__/admin-target.test.ts +12 -0
- package/src/lib/__tests__/command-dx.test.ts +1 -1
- package/src/lib/__tests__/domain-publication.test.ts +138 -0
- package/src/lib/__tests__/idp-session.test.ts +22 -0
- package/src/lib/__tests__/idp.test.ts +36 -4
- package/src/lib/__tests__/instance-candidates.test.ts +1 -3
- package/src/lib/__tests__/instance-target.test.ts +7 -39
- package/src/lib/__tests__/instance.test.ts +6 -0
- package/src/lib/__tests__/log-errors.test.ts +64 -0
- package/src/lib/__tests__/studio-server-deps.test.ts +53 -16
- package/src/lib/__tests__/update.test.ts +113 -8
- package/src/lib/__tests__/view-open-intent.test.ts +0 -2
- package/src/lib/__tests__/view-server.test.ts +1 -1
- package/src/lib/__tests__/view-session.test.ts +1 -1
- package/src/lib/admin-instance.ts +14 -8
- package/src/lib/admin-target.ts +21 -2
- package/src/lib/command-dx.ts +9 -9
- package/src/lib/domain-publication.ts +104 -0
- package/src/lib/failure-debug.ts +26 -0
- package/src/lib/idp.ts +39 -4
- package/src/lib/instance-candidates.ts +2 -2
- package/src/lib/instance-target.ts +3 -14
- package/src/lib/instance.ts +22 -12
- package/src/lib/log.ts +16 -9
- package/src/lib/login-flow.ts +13 -3
- package/src/lib/meta.ts +2 -2
- package/src/lib/provision-instance.ts +34 -57
- package/src/lib/update.ts +124 -30
- package/src/lib/validation.ts +2 -2
- package/src/lib/view/resolve.ts +1 -1
- package/src/program/__tests__/program.test.ts +20 -4
- package/src/program/build.ts +2 -2
- package/src/setup/__tests__/instance-step.test.ts +27 -10
- package/src/setup/steps/instance.ts +22 -15
- package/studio/client/dist/assets/{elk-api-D2xgMJvi.js → elk-api-ByzoDZWH.js} +1 -1
- package/studio/client/dist/assets/index-B0RCgUOT.js +8 -0
- package/studio/client/dist/assets/index-BiGR8Oo9.css +1 -0
- package/studio/client/dist/assets/index-N8J8EKlB.js +81 -0
- package/studio/client/dist/index.html +2 -2
- package/studio/package.json +5 -10
- package/studio/server/agent/ask.test.ts +1 -1
- package/studio/server/agent/bridge/grant.test.ts +1 -1
- package/studio/server/agent/bridge/routes.test.ts +1 -1
- package/studio/server/agent/bridge/stdio.ts +8 -1
- package/studio/server/agent/conversation.test.ts +10 -5
- package/studio/server/agent/conversation.ts +56 -8
- package/studio/server/agent/harness/claude/adapter.test.ts +15 -5
- package/studio/server/agent/harness/codex/loadout.ts +18 -21
- package/studio/server/agent/harness/gateway/config.ts +20 -18
- package/studio/server/agent/harness/selection.ts +9 -1
- package/studio/server/agent/prompts/anchors.test.ts +32 -36
- package/studio/server/agent/prompts/anchors.ts +20 -49
- package/studio/server/agent/prompts/system.test.ts +3 -2
- package/studio/server/agent/prompts/system.ts +6 -5
- package/studio/server/agent/prompts/turn.ts +13 -4
- package/studio/server/agent/routes.test.ts +1 -1
- package/studio/server/agent/run/completion.ts +2 -2
- package/studio/server/agent/run/coordinator.test.ts +1 -1
- package/studio/server/agent/run/preparation.ts +6 -5
- package/studio/server/agent/run/transcript.test.ts +35 -1
- package/studio/server/agent/run/transcript.ts +169 -3
- package/studio/server/agent/run/usage.test.ts +24 -0
- package/studio/server/agent/run/usage.ts +26 -1
- package/studio/server/api/canvas.ts +43 -0
- package/studio/server/api/comments.ts +85 -0
- package/studio/server/api/context.ts +61 -0
- package/studio/server/api/deployment.ts +39 -0
- package/studio/server/api/documents.ts +55 -0
- package/studio/server/api/domain.ts +59 -0
- package/studio/server/api/http.ts +45 -0
- package/studio/server/api/project.ts +57 -0
- package/studio/server/api/schema.ts +12 -0
- package/studio/server/api/views.ts +57 -0
- package/studio/server/api/workspace.ts +60 -0
- package/studio/server/api-agent-loadout.test.ts +1 -1
- package/studio/server/api.test.ts +147 -0
- package/studio/server/api.ts +20 -422
- package/studio/server/cache.test.ts +122 -0
- package/studio/server/cache.ts +162 -11
- package/studio/server/cli-consumers.test.ts +494 -0
- package/studio/server/cli.test.ts +88 -0
- package/studio/server/cli.ts +240 -0
- package/studio/server/client-package.ts +25 -1
- package/studio/server/domain.test.ts +50 -40
- package/studio/server/domain.ts +83 -52
- package/studio/server/environment/dotenv-preview.test.ts +39 -0
- package/studio/server/environment/dotenv-preview.ts +27 -0
- package/studio/server/environment/files.test.ts +53 -0
- package/studio/server/{state/env.ts → environment/files.ts} +7 -48
- package/studio/server/{state → handoff}/copy.ts +21 -6
- package/studio/server/handoff/service.test.ts +22 -0
- package/studio/server/{state/handoff.ts → handoff/service.ts} +21 -14
- package/studio/server/index.ts +1 -1
- package/studio/server/instances/active.ts +72 -0
- package/studio/server/instances/deploy-record.test.ts +31 -0
- package/studio/server/instances/deploy-record.ts +37 -0
- package/studio/server/instances/deploy.ts +56 -0
- package/studio/server/{state/instance.test.ts → instances/probe.test.ts} +25 -21
- package/studio/server/instances/probe.ts +67 -0
- package/studio/server/instances/status.test.ts +12 -0
- package/studio/server/instances/status.ts +49 -0
- package/studio/server/introspect/anatomy/client-tree.ts +57 -0
- package/studio/server/introspect/anatomy/env-fields.ts +66 -0
- package/studio/server/introspect/anatomy/schema-definition.ts +53 -0
- package/studio/server/introspect/anatomy/source.ts +181 -0
- package/studio/server/introspect/anatomy/views/routes.ts +114 -0
- package/studio/server/introspect/anatomy/views.ts +53 -0
- package/studio/server/introspect/anatomy-extras.test.ts +106 -35
- package/studio/server/introspect/anatomy-extras.ts +7 -726
- package/studio/server/introspect/anatomy.test.ts +22 -14
- package/studio/server/introspect/anatomy.ts +52 -28
- package/studio/server/introspect/bundle.ts +15 -9
- package/studio/server/introspect/canonical-schema.test.ts +93 -309
- package/studio/server/introspect/canonical-schema.ts +257 -425
- package/studio/server/introspect/config-preview.test.ts +32 -0
- package/studio/server/introspect/config-preview.ts +119 -0
- package/studio/server/introspect/core-extractor.ts +18 -127
- package/studio/server/introspect/core.ts +1 -2
- package/studio/server/introspect/diff.test.ts +18 -18
- package/studio/server/introspect/diff.ts +44 -90
- package/studio/server/introspect/extractor.ts +23 -116
- package/studio/server/introspect/hash.ts +5 -2
- package/studio/server/introspect/overlay-tsmorph.test.ts +119 -240
- package/studio/server/introspect/overlay-tsmorph.ts +7 -1305
- package/studio/server/introspect/overlay.test.ts +26 -54
- package/studio/server/introspect/overlay.ts +10 -37
- package/studio/server/introspect/revision.test.ts +54 -0
- package/studio/server/introspect/revision.ts +49 -0
- package/studio/server/introspect/runtime.test.ts +83 -143
- package/studio/server/introspect/runtime.ts +40 -11
- package/studio/server/introspect/schema-ir-json.test.ts +70 -0
- package/studio/server/introspect/schema-ir-json.ts +151 -0
- package/studio/server/introspect/schema-refs.test.ts +43 -88
- package/studio/server/introspect/schema-refs.ts +14 -49
- package/studio/server/introspect/source-overlay/annotations.ts +14 -0
- package/studio/server/introspect/source-overlay/handlers.ts +161 -0
- package/studio/server/introspect/source-overlay/kernel-calls.ts +46 -0
- package/studio/server/introspect/source-overlay/project.ts +248 -0
- package/studio/server/introspect/source-overlay/spans.ts +330 -0
- package/studio/server/json.ts +46 -0
- package/studio/server/lifecycle.ts +5 -1
- package/studio/server/sse.test.ts +26 -0
- package/studio/server/sse.ts +4 -1
- package/studio/server/state/baseline.test.ts +223 -7
- package/studio/server/state/baseline.ts +148 -34
- package/studio/server/state/comments.test.ts +66 -1
- package/studio/server/state/comments.ts +173 -10
- package/studio/server/state/context.ts +34 -2
- package/studio/server/state/documents.ts +42 -1
- package/studio/server/state/integrations.ts +27 -1
- package/studio/server/state/layout.test.ts +34 -0
- package/studio/server/state/layout.ts +44 -17
- package/studio/server/state/settings.ts +29 -22
- package/studio/server/state/store.test.ts +122 -0
- package/studio/server/state/store.ts +66 -17
- package/studio/server/state/visibility.ts +22 -11
- package/studio/server/views/model.test.ts +57 -0
- package/studio/server/views/model.ts +66 -0
- package/studio/server/views/runtime.ts +40 -0
- package/studio/server/views/selection-repository.test.ts +31 -0
- package/studio/server/views/selection-repository.ts +60 -0
- package/studio/server/views/session.test.ts +73 -0
- package/studio/server/views/session.ts +166 -0
- package/studio/server/views/target.test.ts +78 -0
- package/studio/server/views/target.ts +222 -0
- package/studio/server/watch.test.ts +11 -11
- package/studio/server/watch.ts +9 -5
- package/studio/server/workspace/catalog.test.ts +17 -0
- package/studio/server/workspace/catalog.ts +43 -0
- package/studio/server/{state → workspace}/create.test.ts +1 -1
- package/studio/server/{state → workspace}/create.ts +1 -1
- package/studio/server/{state → workspace}/git.ts +1 -1
- package/studio/server/workspace/updates.ts +83 -0
- package/studio/server/workspace-state.ts +1 -1
- package/studio/server/workspace-watch.ts +2 -2
- package/studio/shared/contracts/README.md +16 -0
- package/studio/shared/contracts/agent.ts +270 -0
- package/studio/shared/contracts/runtime.ts +50 -0
- package/studio/shared/contracts/schema.ts +393 -0
- package/studio/shared/contracts/surface.test.ts +317 -0
- package/studio/shared/contracts/workspace.ts +265 -0
- package/studio/shared/layout.test.ts +116 -0
- package/studio/shared/schema/identity.test.ts +34 -0
- package/studio/shared/schema/identity.ts +68 -0
- package/studio/shared/types.ts +11 -1099
- package/studio/tsconfig.json +1 -1
- package/viewer/dist/main.js +40 -61
- package/.check-workspace.cjs +0 -40
- package/src/commands/__tests__/instance-create-hosts.test.ts +0 -29
- package/studio/client/dist/assets/index-BMdnsIJA.css +0 -1
- package/studio/client/dist/assets/index-D-vRV8w7.js +0 -8
- package/studio/client/dist/assets/index-LGSWRrk8.js +0 -81
- package/studio/server/state/catalog.ts +0 -117
- package/studio/server/state/instance.ts +0 -280
- package/studio/server/state/updates.ts +0 -63
- package/studio/server/state/views.test.ts +0 -286
- package/studio/server/state/views.ts +0 -535
|
@@ -5,8 +5,9 @@ import { buildSystemPrompt } from './system'
|
|
|
5
5
|
test('embedded agents receive the current SDK layout contract', () => {
|
|
6
6
|
const prompt = buildSystemPrompt({ bridge: false })
|
|
7
7
|
|
|
8
|
-
expect(prompt).toContain('
|
|
8
|
+
expect(prompt).toContain('modular Actions and Workflows')
|
|
9
9
|
expect(prompt).toContain('pre-Kernel-V2 APIs or layouts')
|
|
10
|
-
expect(prompt).toContain('
|
|
10
|
+
expect(prompt).toContain('Runtime/Application entries')
|
|
11
|
+
expect(prompt).toContain('do not restore defineDomain or Interface-era layouts')
|
|
11
12
|
expect(prompt).not.toContain('schema/ runtime/ views/')
|
|
12
13
|
})
|
|
@@ -28,16 +28,17 @@ export function buildSystemPrompt(options: { bridge: boolean }): string {
|
|
|
28
28
|
' and **astrale-cli** when you run the `astrale` CLI. Treat those current skills and',
|
|
29
29
|
' the project itself as authoritative; do not restore pre-Kernel-V2 APIs or layouts.',
|
|
30
30
|
'- Prefer the project’s existing semantic modules over inventing new structure. Current',
|
|
31
|
-
' SDK projects
|
|
32
|
-
'
|
|
33
|
-
'
|
|
31
|
+
' SDK projects author Schema definitions, modular Actions and Workflows, a Runtime,',
|
|
32
|
+
' and an Application. Wire modules through their semantic SDK facades and explicit',
|
|
33
|
+
' Runtime/Application entries; do not restore defineDomain or Interface-era layouts.',
|
|
34
34
|
'- The studio re-renders automatically as you save files — never start, build, or refresh',
|
|
35
35
|
' anything for the UI to update.',
|
|
36
36
|
'- Sanity-check schema/handler edits with `pnpm typecheck` (or `tsgo --noEmit`).',
|
|
37
37
|
'- You ARE allowed to run the shell. When a thread asks you to **deploy or install**, do',
|
|
38
38
|
' it yourself: `pnpm prod` (managed deploy + install) or the `astrale` CLI. The user is',
|
|
39
|
-
' already authenticated (`astrale auth`). After deploying, VERIFY:
|
|
40
|
-
' must
|
|
39
|
+
' already authenticated (`astrale auth`). After deploying, VERIFY:',
|
|
40
|
+
' `curl <svc-url>/.well-known/astrale/domain.json` must publish this domain origin, and a',
|
|
41
|
+
' live smoke call (create a node, call a method) should work',
|
|
41
42
|
' — report the URL and what you verified. Long-running commands are fine.',
|
|
42
43
|
'- SINGLE HEADLESS TURN — you are not a resident session: there is NO background loop and NO',
|
|
43
44
|
' wake-on-completion. A shell started with run_in_background (or any “notify me when it',
|
|
@@ -1,12 +1,20 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
Comment,
|
|
3
|
+
ContextItem,
|
|
4
|
+
DocMeta,
|
|
5
|
+
SchemaIR,
|
|
6
|
+
SchemaOverlay,
|
|
7
|
+
SchemaRevision,
|
|
8
|
+
} from '../../../shared/types'
|
|
2
9
|
|
|
3
|
-
import { buildCopyMarkdown } from '../../
|
|
10
|
+
import { buildCopyMarkdown } from '../../handoff/copy'
|
|
4
11
|
import { resolveThreadAnchors } from './anchors'
|
|
5
12
|
|
|
6
13
|
export interface TurnParts {
|
|
7
14
|
origin: string
|
|
8
15
|
root: string
|
|
9
|
-
|
|
16
|
+
renderFingerprint: string
|
|
17
|
+
schemaRevision?: SchemaRevision
|
|
10
18
|
awaitingThreads: Comment[]
|
|
11
19
|
userContext: ContextItem[]
|
|
12
20
|
autoContext: ContextItem[]
|
|
@@ -22,7 +30,8 @@ export function buildTurnPrompt(parts: TurnParts): string {
|
|
|
22
30
|
const body = buildCopyMarkdown({
|
|
23
31
|
origin: parts.origin,
|
|
24
32
|
root: parts.root,
|
|
25
|
-
|
|
33
|
+
renderFingerprint: parts.renderFingerprint,
|
|
34
|
+
schemaRevision: parts.schemaRevision,
|
|
26
35
|
openComments: parts.awaitingThreads,
|
|
27
36
|
userContext: parts.userContext,
|
|
28
37
|
autoContext: parts.autoContext,
|
|
@@ -26,7 +26,7 @@ function fixture() {
|
|
|
26
26
|
roots.push(root)
|
|
27
27
|
mkdirSync(join(root, 'schema'))
|
|
28
28
|
writeFileSync(join(root, 'astrale.config.ts'), 'export default {}\n')
|
|
29
|
-
writeFileSync(join(root, '
|
|
29
|
+
writeFileSync(join(root, 'application.ts'), 'export default {}\n')
|
|
30
30
|
writeFileSync(join(root, 'schema/index.ts'), 'export const Test = {}\n')
|
|
31
31
|
const handle = registerDomain(root)!
|
|
32
32
|
domainIds.push(handle.id)
|
|
@@ -31,7 +31,7 @@ export async function completeRun(
|
|
|
31
31
|
settings,
|
|
32
32
|
session,
|
|
33
33
|
resume,
|
|
34
|
-
|
|
34
|
+
renderFingerprint,
|
|
35
35
|
model,
|
|
36
36
|
effort,
|
|
37
37
|
harnessEnv,
|
|
@@ -141,7 +141,7 @@ export async function completeRun(
|
|
|
141
141
|
result.finalText.trim()
|
|
142
142
|
) {
|
|
143
143
|
try {
|
|
144
|
-
const merged = mergeReply(root,
|
|
144
|
+
const merged = mergeReply(root, renderFingerprint, result.finalText, {
|
|
145
145
|
skipByComment: liveByComment,
|
|
146
146
|
})
|
|
147
147
|
run.merge = merged
|
|
@@ -58,7 +58,7 @@ function fixture(): DomainHandle {
|
|
|
58
58
|
roots.push(root)
|
|
59
59
|
mkdirSync(join(root, 'schema'))
|
|
60
60
|
writeFileSync(join(root, 'astrale.config.ts'), 'export default {}\n')
|
|
61
|
-
writeFileSync(join(root, '
|
|
61
|
+
writeFileSync(join(root, 'application.ts'), 'export default {}\n')
|
|
62
62
|
writeFileSync(join(root, 'schema/index.ts'), 'export const Test = {}\n')
|
|
63
63
|
const handle = registerDomain(root)!
|
|
64
64
|
domainIds.push(handle.id)
|
|
@@ -13,10 +13,10 @@ import type { Bridge } from '../bridge/grant'
|
|
|
13
13
|
import type { AgentHarness } from '../harness/adapter'
|
|
14
14
|
|
|
15
15
|
import { getBundle } from '../../cache'
|
|
16
|
+
import { refreshAuto } from '../../handoff/service'
|
|
16
17
|
import { readComments } from '../../state/comments'
|
|
17
18
|
import { readContext } from '../../state/context'
|
|
18
19
|
import { listDocuments } from '../../state/documents'
|
|
19
|
-
import { refreshAuto } from '../../state/handoff'
|
|
20
20
|
import { startBridge } from '../bridge/grant'
|
|
21
21
|
import { readConversation } from '../conversation'
|
|
22
22
|
import { getHarness, resolveHarnessConfiguration } from '../harness/selection'
|
|
@@ -36,7 +36,7 @@ export interface PreparedRun {
|
|
|
36
36
|
settings: StudioSettings
|
|
37
37
|
session: ReturnType<typeof readConversation>
|
|
38
38
|
resume?: string
|
|
39
|
-
|
|
39
|
+
renderFingerprint: string
|
|
40
40
|
model?: string
|
|
41
41
|
effort?: AgentEffort
|
|
42
42
|
harnessEnv: Record<string, string>
|
|
@@ -80,7 +80,7 @@ export async function prepareRun(
|
|
|
80
80
|
const bundle = await getBundle(domainId)
|
|
81
81
|
if (controller.signal.aborted) return { error: 'agent run canceled during setup' }
|
|
82
82
|
|
|
83
|
-
const
|
|
83
|
+
const renderFingerprint = bundle?.renderFingerprint ?? ''
|
|
84
84
|
const context = readContext(root)
|
|
85
85
|
const documents = listDocuments(root)
|
|
86
86
|
const configuration = await resolveHarnessConfiguration(root, harness)
|
|
@@ -96,7 +96,8 @@ export async function prepareRun(
|
|
|
96
96
|
: buildTurnPrompt({
|
|
97
97
|
origin: bundle?.overlay.origin ?? domainId,
|
|
98
98
|
root,
|
|
99
|
-
|
|
99
|
+
renderFingerprint,
|
|
100
|
+
schemaRevision: bundle?.schemaRevision,
|
|
100
101
|
awaitingThreads: awaiting,
|
|
101
102
|
userContext: context.user,
|
|
102
103
|
autoContext: context.auto.filter((item) => item.includeInHandoff),
|
|
@@ -150,7 +151,7 @@ export async function prepareRun(
|
|
|
150
151
|
settings,
|
|
151
152
|
session,
|
|
152
153
|
resume,
|
|
153
|
-
|
|
154
|
+
renderFingerprint,
|
|
154
155
|
model,
|
|
155
156
|
effort,
|
|
156
157
|
harnessEnv,
|
|
@@ -5,6 +5,7 @@ import { join } from 'node:path'
|
|
|
5
5
|
|
|
6
6
|
import type { AgentRun } from '../../../shared/types'
|
|
7
7
|
|
|
8
|
+
import { asJsonRecord } from '../../json'
|
|
8
9
|
import { readJson, writeJson } from '../../state/store'
|
|
9
10
|
import { currentRun, hydrateRun } from './live-state'
|
|
10
11
|
import { readLastRun } from './transcript'
|
|
@@ -37,7 +38,7 @@ test('reconciles a run orphaned by restart and hydrates its terminal snapshot',
|
|
|
37
38
|
sessionId: 'thread-1',
|
|
38
39
|
error: expect.stringContaining('studio restarted'),
|
|
39
40
|
})
|
|
40
|
-
expect(readJson
|
|
41
|
+
expect(readJson(root, '.cache/agent/last-run.json', asJsonRecord, {}).status).toBe('interrupted')
|
|
41
42
|
|
|
42
43
|
hydrateRun(domainId, root)
|
|
43
44
|
expect(currentRun(domainId)).toMatchObject({
|
|
@@ -45,3 +46,36 @@ test('reconciles a run orphaned by restart and hydrates its terminal snapshot',
|
|
|
45
46
|
sessionId: 'thread-1',
|
|
46
47
|
})
|
|
47
48
|
})
|
|
49
|
+
|
|
50
|
+
test('admits future run fields but rejects malformed persisted run structure', () => {
|
|
51
|
+
const root = mkdtempSync(join(tmpdir(), 'studio-agent-transcript-boundary-'))
|
|
52
|
+
roots.push(root)
|
|
53
|
+
const domainId = `domain-${crypto.randomUUID()}`
|
|
54
|
+
writeJson(root, '.cache/agent/last-run.json', {
|
|
55
|
+
id: crypto.randomUUID(),
|
|
56
|
+
domainId,
|
|
57
|
+
harness: 'codex',
|
|
58
|
+
status: 'succeeded',
|
|
59
|
+
createdAt: '2026-08-20T00:00:00.000Z',
|
|
60
|
+
summary: 'finished work',
|
|
61
|
+
targetCommentIds: [],
|
|
62
|
+
events: [],
|
|
63
|
+
futureRunField: { version: 2 },
|
|
64
|
+
})
|
|
65
|
+
expect(readLastRun(domainId, root)).toMatchObject({
|
|
66
|
+
status: 'succeeded',
|
|
67
|
+
summary: 'finished work',
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
writeJson(root, '.cache/agent/last-run.json', {
|
|
71
|
+
id: crypto.randomUUID(),
|
|
72
|
+
domainId,
|
|
73
|
+
harness: 'codex',
|
|
74
|
+
status: 42,
|
|
75
|
+
createdAt: '2026-08-20T00:00:00.000Z',
|
|
76
|
+
summary: 'corrupt',
|
|
77
|
+
targetCommentIds: [],
|
|
78
|
+
events: [],
|
|
79
|
+
})
|
|
80
|
+
expect(readLastRun(domainId, root)).toBeNull()
|
|
81
|
+
})
|
|
@@ -1,10 +1,176 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
AGENT_ACCESS_LEVELS,
|
|
3
|
+
AGENT_EFFORT_LEVELS,
|
|
4
|
+
type AgentEvent,
|
|
5
|
+
type AgentPromptSnapshot,
|
|
6
|
+
type AgentRun,
|
|
7
|
+
type MergeResult,
|
|
8
|
+
} from '../../../shared/types'
|
|
9
|
+
import { asBoolean, asFiniteNumber, asJsonRecord, asString, asStringArray } from '../../json'
|
|
3
10
|
import { readJson, writeJson } from '../../state/store'
|
|
4
11
|
|
|
5
12
|
const LAST_RUN_FILE = '.cache/agent/last-run.json'
|
|
6
13
|
const runFile = (id: string) => `.cache/agent/runs/${id}.json`
|
|
7
14
|
|
|
15
|
+
const RUN_STATUSES = new Set<AgentRun['status']>([
|
|
16
|
+
'queued',
|
|
17
|
+
'running',
|
|
18
|
+
'succeeded',
|
|
19
|
+
'failed',
|
|
20
|
+
'canceled',
|
|
21
|
+
'interrupted',
|
|
22
|
+
])
|
|
23
|
+
const EVENT_KINDS = new Set<AgentEvent['kind']>([
|
|
24
|
+
'status',
|
|
25
|
+
'thinking',
|
|
26
|
+
'message',
|
|
27
|
+
'tool',
|
|
28
|
+
'reply',
|
|
29
|
+
'error',
|
|
30
|
+
])
|
|
31
|
+
|
|
32
|
+
function decodeAgentEvent(value: unknown): AgentEvent | undefined {
|
|
33
|
+
const record = asJsonRecord(value)
|
|
34
|
+
const id = asString(record?.id)
|
|
35
|
+
const ts = asString(record?.ts)
|
|
36
|
+
const kind = asString(record?.kind)
|
|
37
|
+
const text = asString(record?.text)
|
|
38
|
+
if (!id || !ts || !kind || !EVENT_KINDS.has(kind as AgentEvent['kind']) || text === undefined)
|
|
39
|
+
return undefined
|
|
40
|
+
const tool = asString(record?.tool)
|
|
41
|
+
const target = asString(record?.target)
|
|
42
|
+
const commentId = asString(record?.commentId)
|
|
43
|
+
return {
|
|
44
|
+
id,
|
|
45
|
+
ts,
|
|
46
|
+
kind: kind as AgentEvent['kind'],
|
|
47
|
+
text,
|
|
48
|
+
...(tool === undefined ? {} : { tool }),
|
|
49
|
+
...(target === undefined ? {} : { target }),
|
|
50
|
+
...(commentId === undefined ? {} : { commentId }),
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function decodeMergeResult(value: unknown): MergeResult | undefined {
|
|
55
|
+
const record = asJsonRecord(value)
|
|
56
|
+
const merged = asFiniteNumber(record?.merged)
|
|
57
|
+
const closed = asFiniteNumber(record?.closed)
|
|
58
|
+
const unknownIds = asStringArray(record?.unknownIds)
|
|
59
|
+
const schemaMismatch = asBoolean(record?.schemaMismatch)
|
|
60
|
+
if (
|
|
61
|
+
merged === undefined ||
|
|
62
|
+
!Number.isInteger(merged) ||
|
|
63
|
+
closed === undefined ||
|
|
64
|
+
!Number.isInteger(closed) ||
|
|
65
|
+
!unknownIds ||
|
|
66
|
+
schemaMismatch === undefined
|
|
67
|
+
) {
|
|
68
|
+
return undefined
|
|
69
|
+
}
|
|
70
|
+
const pastedSchemaVersion = asString(record?.pastedSchemaVersion)
|
|
71
|
+
return {
|
|
72
|
+
merged,
|
|
73
|
+
closed,
|
|
74
|
+
unknownIds,
|
|
75
|
+
schemaMismatch,
|
|
76
|
+
...(pastedSchemaVersion === undefined ? {} : { pastedSchemaVersion }),
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function decodePrompt(value: unknown): AgentPromptSnapshot | undefined {
|
|
81
|
+
const record = asJsonRecord(value)
|
|
82
|
+
const createdAt = asString(record?.createdAt)
|
|
83
|
+
const systemPrompt = asString(record?.systemPrompt)
|
|
84
|
+
const turnPrompt = asString(record?.turnPrompt)
|
|
85
|
+
const firstTurn = asBoolean(record?.firstTurn)
|
|
86
|
+
const resumed = asBoolean(record?.resumed)
|
|
87
|
+
const mcpTools = asStringArray(record?.mcpTools)
|
|
88
|
+
if (
|
|
89
|
+
!createdAt ||
|
|
90
|
+
systemPrompt === undefined ||
|
|
91
|
+
turnPrompt === undefined ||
|
|
92
|
+
firstTurn === undefined ||
|
|
93
|
+
resumed === undefined ||
|
|
94
|
+
!mcpTools
|
|
95
|
+
) {
|
|
96
|
+
return undefined
|
|
97
|
+
}
|
|
98
|
+
const sessionId = asString(record?.sessionId)
|
|
99
|
+
const model = asString(record?.model)
|
|
100
|
+
const effort = AGENT_EFFORT_LEVELS.find((candidate) => candidate === record?.effort)
|
|
101
|
+
const access = AGENT_ACCESS_LEVELS.find((candidate) => candidate === record?.access)
|
|
102
|
+
return {
|
|
103
|
+
createdAt,
|
|
104
|
+
systemPrompt,
|
|
105
|
+
turnPrompt,
|
|
106
|
+
firstTurn,
|
|
107
|
+
resumed,
|
|
108
|
+
mcpTools,
|
|
109
|
+
...(sessionId === undefined ? {} : { sessionId }),
|
|
110
|
+
...(model === undefined ? {} : { model }),
|
|
111
|
+
...(effort === undefined ? {} : { effort }),
|
|
112
|
+
...(access === undefined ? {} : { access }),
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function decodeAgentRun(value: unknown): AgentRun | undefined {
|
|
117
|
+
const record = asJsonRecord(value)
|
|
118
|
+
const id = asString(record?.id)
|
|
119
|
+
const domainId = asString(record?.domainId)
|
|
120
|
+
const harness = asString(record?.harness)
|
|
121
|
+
const status = asString(record?.status)
|
|
122
|
+
const createdAt = asString(record?.createdAt)
|
|
123
|
+
const summary = asString(record?.summary)
|
|
124
|
+
const targetCommentIds = asStringArray(record?.targetCommentIds)
|
|
125
|
+
if (
|
|
126
|
+
!id ||
|
|
127
|
+
!domainId ||
|
|
128
|
+
!harness ||
|
|
129
|
+
!status ||
|
|
130
|
+
!RUN_STATUSES.has(status as AgentRun['status']) ||
|
|
131
|
+
!createdAt ||
|
|
132
|
+
summary === undefined ||
|
|
133
|
+
!targetCommentIds ||
|
|
134
|
+
!Array.isArray(record?.events)
|
|
135
|
+
) {
|
|
136
|
+
return undefined
|
|
137
|
+
}
|
|
138
|
+
const events = record.events.flatMap((event) => {
|
|
139
|
+
const decoded = decodeAgentEvent(event)
|
|
140
|
+
return decoded ? [decoded] : []
|
|
141
|
+
})
|
|
142
|
+
const finishedAt = asString(record.finishedAt)
|
|
143
|
+
const sessionId = asString(record.sessionId)
|
|
144
|
+
const resumed = asBoolean(record.resumed)
|
|
145
|
+
const costUsd = asFiniteNumber(record.costUsd)
|
|
146
|
+
const numTurns = asFiniteNumber(record.numTurns)
|
|
147
|
+
const tokens = asFiniteNumber(record.tokens)
|
|
148
|
+
const error = asString(record.error)
|
|
149
|
+
const liveReplies = asFiniteNumber(record.liveReplies)
|
|
150
|
+
const merge = decodeMergeResult(record.merge)
|
|
151
|
+
const prompt = decodePrompt(record.prompt)
|
|
152
|
+
return {
|
|
153
|
+
id,
|
|
154
|
+
domainId,
|
|
155
|
+
harness,
|
|
156
|
+
status: status as AgentRun['status'],
|
|
157
|
+
createdAt,
|
|
158
|
+
summary,
|
|
159
|
+
targetCommentIds,
|
|
160
|
+
events,
|
|
161
|
+
...(finishedAt === undefined ? {} : { finishedAt }),
|
|
162
|
+
...(sessionId === undefined ? {} : { sessionId }),
|
|
163
|
+
...(resumed === undefined ? {} : { resumed }),
|
|
164
|
+
...(costUsd === undefined ? {} : { costUsd }),
|
|
165
|
+
...(numTurns === undefined ? {} : { numTurns }),
|
|
166
|
+
...(tokens === undefined ? {} : { tokens }),
|
|
167
|
+
...(error === undefined ? {} : { error }),
|
|
168
|
+
...(liveReplies === undefined ? {} : { liveReplies }),
|
|
169
|
+
...(merge === undefined ? {} : { merge }),
|
|
170
|
+
...(prompt === undefined ? {} : { prompt }),
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
8
174
|
/** Persist the latest run pointer and, when terminal, its transcript. */
|
|
9
175
|
export function persistRun(root: string, run: AgentRun, transcript = false): void {
|
|
10
176
|
try {
|
|
@@ -17,7 +183,7 @@ export function persistRun(root: string, run: AgentRun, transcript = false): voi
|
|
|
17
183
|
|
|
18
184
|
/** Rehydrate the latest run and reconcile one orphaned by a Studio restart. */
|
|
19
185
|
export function readLastRun(domainId: string, root: string): AgentRun | null {
|
|
20
|
-
const last = readJson
|
|
186
|
+
const last = readJson(root, LAST_RUN_FILE, decodeAgentRun, null)
|
|
21
187
|
if (!last || last.domainId !== domainId) return null
|
|
22
188
|
if (last.status === 'running' || last.status === 'queued') {
|
|
23
189
|
last.status = 'interrupted'
|
|
@@ -5,6 +5,7 @@ import { join } from 'node:path'
|
|
|
5
5
|
|
|
6
6
|
import type { AgentRun } from '../../../shared/types'
|
|
7
7
|
|
|
8
|
+
import { writeJson } from '../../state/store'
|
|
8
9
|
import { readUsage, recordRun } from './usage'
|
|
9
10
|
|
|
10
11
|
const roots: string[] = []
|
|
@@ -44,3 +45,26 @@ test('records only reported usage and accumulates domain totals', () => {
|
|
|
44
45
|
lastCostUsd: 0.1,
|
|
45
46
|
})
|
|
46
47
|
})
|
|
48
|
+
|
|
49
|
+
test('normalizes malformed usage fields while accepting future fields', () => {
|
|
50
|
+
const root = mkdtempSync(join(tmpdir(), 'studio-agent-usage-boundary-'))
|
|
51
|
+
roots.push(root)
|
|
52
|
+
writeJson(root, 'usage.json', {
|
|
53
|
+
runs: -2,
|
|
54
|
+
tokens: 'many',
|
|
55
|
+
costUsd: -1,
|
|
56
|
+
lastRunAt: 42,
|
|
57
|
+
lastTokens: -10,
|
|
58
|
+
futureUsageField: { version: 2 },
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
expect(readUsage(root)).toEqual({ runs: 0, tokens: 0, costUsd: 0 })
|
|
62
|
+
|
|
63
|
+
writeJson(root, 'usage.json', {
|
|
64
|
+
runs: 3,
|
|
65
|
+
tokens: 120,
|
|
66
|
+
costUsd: 1.25,
|
|
67
|
+
futureUsageField: { version: 2 },
|
|
68
|
+
})
|
|
69
|
+
expect(readUsage(root)).toEqual({ runs: 3, tokens: 120, costUsd: 1.25 })
|
|
70
|
+
})
|
|
@@ -6,14 +6,39 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import type { AgentRun, DomainUsage } from '../../../shared/types'
|
|
8
8
|
|
|
9
|
+
import { asFiniteNumber, asJsonRecord, asString } from '../../json'
|
|
9
10
|
import { readJson, writeJson } from '../../state/store'
|
|
10
11
|
|
|
11
12
|
const PATH = 'usage.json'
|
|
12
13
|
|
|
13
14
|
const EMPTY: DomainUsage = { runs: 0, tokens: 0, costUsd: 0 }
|
|
14
15
|
|
|
16
|
+
function nonNegative(value: unknown): number | undefined {
|
|
17
|
+
const number = asFiniteNumber(value)
|
|
18
|
+
return number !== undefined && number >= 0 ? number : undefined
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function decodeUsage(value: unknown): DomainUsage | undefined {
|
|
22
|
+
const record = asJsonRecord(value)
|
|
23
|
+
if (!record) return undefined
|
|
24
|
+
const runs = nonNegative(record.runs)
|
|
25
|
+
const tokens = nonNegative(record.tokens)
|
|
26
|
+
const costUsd = nonNegative(record.costUsd)
|
|
27
|
+
const lastRunAt = asString(record.lastRunAt)
|
|
28
|
+
const lastTokens = nonNegative(record.lastTokens)
|
|
29
|
+
const lastCostUsd = nonNegative(record.lastCostUsd)
|
|
30
|
+
return {
|
|
31
|
+
runs: runs !== undefined && Number.isInteger(runs) ? runs : EMPTY.runs,
|
|
32
|
+
tokens: tokens ?? EMPTY.tokens,
|
|
33
|
+
costUsd: costUsd ?? EMPTY.costUsd,
|
|
34
|
+
...(lastRunAt === undefined ? {} : { lastRunAt }),
|
|
35
|
+
...(lastTokens === undefined ? {} : { lastTokens }),
|
|
36
|
+
...(lastCostUsd === undefined ? {} : { lastCostUsd }),
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
15
40
|
export function readUsage(root: string): DomainUsage {
|
|
16
|
-
return
|
|
41
|
+
return readJson(root, PATH, decodeUsage, { ...EMPTY })
|
|
17
42
|
}
|
|
18
43
|
|
|
19
44
|
/** Fold a finished run into the running total. A run with no reported usage (e.g. it
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/** Persisted canvas layout and visibility preference routes. */
|
|
2
|
+
import { getBundle } from '../cache'
|
|
3
|
+
import { readLayout, resetLayout, saveLayout, setNodePositions } from '../state/layout'
|
|
4
|
+
import { readVisibility, resetVisibility, saveVisibility } from '../state/visibility'
|
|
5
|
+
import { badRequest, json, type DomainRouteContext } from './http'
|
|
6
|
+
|
|
7
|
+
export async function handleCanvasRoute(context: DomainRouteContext): Promise<Response | null> {
|
|
8
|
+
const { req, rest, body, handle } = context
|
|
9
|
+
const id = handle.id
|
|
10
|
+
const root = handle.root
|
|
11
|
+
|
|
12
|
+
if (rest === '/layout') {
|
|
13
|
+
const bundle = await getBundle(id)
|
|
14
|
+
if (req.method === 'GET') return json(readLayout(root))
|
|
15
|
+
if (body.action === 'set') {
|
|
16
|
+
return json(setNodePositions(root, body.positions ?? {}, bundle?.renderFingerprint))
|
|
17
|
+
}
|
|
18
|
+
if (body.action === 'save') {
|
|
19
|
+
return json(saveLayout(root, body.positions ?? {}, bundle?.renderFingerprint))
|
|
20
|
+
}
|
|
21
|
+
if (body.action === 'reset') {
|
|
22
|
+
resetLayout(root)
|
|
23
|
+
return json({ ok: true })
|
|
24
|
+
}
|
|
25
|
+
return badRequest('unknown layout action')
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (rest === '/visibility') {
|
|
29
|
+
if (req.method === 'GET') return json(readVisibility(root))
|
|
30
|
+
if (body.action === 'set') {
|
|
31
|
+
return json(
|
|
32
|
+
saveVisibility(root, {
|
|
33
|
+
hidden: body.hidden ?? {},
|
|
34
|
+
showInheritedEdges: body.showInheritedEdges ?? true,
|
|
35
|
+
}),
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
if (body.action === 'reset') return json(resetVisibility(root))
|
|
39
|
+
return badRequest('unknown visibility action')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return null
|
|
43
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/** Schema-anchored review comment routes and agent reply merge. */
|
|
2
|
+
import { getBundle } from '../cache'
|
|
3
|
+
import { schemaRefs } from '../introspect/schema-refs'
|
|
4
|
+
import {
|
|
5
|
+
addThreadEntry,
|
|
6
|
+
deleteComment,
|
|
7
|
+
editThreadEntry,
|
|
8
|
+
markOrphans,
|
|
9
|
+
mergeReply,
|
|
10
|
+
readComments,
|
|
11
|
+
setStatus,
|
|
12
|
+
upsertComment,
|
|
13
|
+
} from '../state/comments'
|
|
14
|
+
import { badRequest, json, notFound, type DomainRouteContext } from './http'
|
|
15
|
+
|
|
16
|
+
export async function handleCommentRoute(context: DomainRouteContext): Promise<Response | null> {
|
|
17
|
+
const { req, rest, body, handle, notify } = context
|
|
18
|
+
const id = handle.id
|
|
19
|
+
const root = handle.root
|
|
20
|
+
|
|
21
|
+
if (rest === '/comments') {
|
|
22
|
+
if (req.method === 'GET') {
|
|
23
|
+
const bundle = await getBundle(id)
|
|
24
|
+
const valid = new Set<string>(bundle ? schemaRefs(bundle) : [])
|
|
25
|
+
for (const key of Object.keys(bundle?.overlay.sourceSpans ?? {})) valid.add(key)
|
|
26
|
+
const store = readComments(root)
|
|
27
|
+
for (const comment of store.comments) {
|
|
28
|
+
for (const anchor of comment.anchorRefs) {
|
|
29
|
+
if (anchor.kind !== 'schema') valid.add(anchor.ref)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return json(markOrphans(root, [...valid]))
|
|
33
|
+
}
|
|
34
|
+
if (body.action === 'create') {
|
|
35
|
+
const bundle = await getBundle(id)
|
|
36
|
+
const comment = upsertComment(root, {
|
|
37
|
+
anchors: body.anchors ?? [],
|
|
38
|
+
anchorRefs: body.anchorRefs ?? [],
|
|
39
|
+
text: body.text,
|
|
40
|
+
firstRole: body.firstRole,
|
|
41
|
+
type: body.type,
|
|
42
|
+
options: body.options,
|
|
43
|
+
schemaVersion: bundle?.renderFingerprint,
|
|
44
|
+
})
|
|
45
|
+
notify({ type: 'comments', domainId: id })
|
|
46
|
+
return json(comment)
|
|
47
|
+
}
|
|
48
|
+
if (body.action === 'reply') {
|
|
49
|
+
const comment = addThreadEntry(root, body.id, body.entry)
|
|
50
|
+
notify({ type: 'comments', domainId: id })
|
|
51
|
+
return comment ? json(comment) : notFound()
|
|
52
|
+
}
|
|
53
|
+
if (body.action === 'edit') {
|
|
54
|
+
const comment = editThreadEntry(root, body.id, body.entryId, String(body.text ?? ''))
|
|
55
|
+
notify({ type: 'comments', domainId: id })
|
|
56
|
+
return comment ? json(comment) : notFound()
|
|
57
|
+
}
|
|
58
|
+
if (body.action === 'status') {
|
|
59
|
+
const comment = setStatus(root, body.id, body.status, body.closeNote)
|
|
60
|
+
notify({ type: 'comments', domainId: id })
|
|
61
|
+
return comment ? json(comment) : notFound()
|
|
62
|
+
}
|
|
63
|
+
if (body.action === 'delete') {
|
|
64
|
+
const ok = deleteComment(root, body.id)
|
|
65
|
+
notify({ type: 'comments', domainId: id })
|
|
66
|
+
return json({ ok })
|
|
67
|
+
}
|
|
68
|
+
return badRequest('unknown comments action')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (rest === '/comments/merge' && req.method === 'POST') {
|
|
72
|
+
const bundle = await getBundle(id)
|
|
73
|
+
try {
|
|
74
|
+
const result = mergeReply(root, bundle?.renderFingerprint ?? '', String(body.text ?? ''), {
|
|
75
|
+
dedupeAuthorText: true,
|
|
76
|
+
})
|
|
77
|
+
notify({ type: 'comments', domainId: id })
|
|
78
|
+
return json(result)
|
|
79
|
+
} catch (error: any) {
|
|
80
|
+
return badRequest(String(error?.message ?? error))
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return null
|
|
85
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/** Authored/automatic agent context and copy-handoff routes. */
|
|
2
|
+
import { getBundle } from '../cache'
|
|
3
|
+
import { buildCopyMarkdown } from '../handoff/copy'
|
|
4
|
+
import { refreshAuto } from '../handoff/service'
|
|
5
|
+
import { readComments } from '../state/comments'
|
|
6
|
+
import {
|
|
7
|
+
addUserContext,
|
|
8
|
+
deleteContext,
|
|
9
|
+
readContext,
|
|
10
|
+
setAutoInclude,
|
|
11
|
+
updateContext,
|
|
12
|
+
} from '../state/context'
|
|
13
|
+
import { listDocuments } from '../state/documents'
|
|
14
|
+
import { badRequest, json, notFound, type DomainRouteContext } from './http'
|
|
15
|
+
|
|
16
|
+
export async function handleContextRoute(context: DomainRouteContext): Promise<Response | null> {
|
|
17
|
+
const { req, rest, body, handle, notify } = context
|
|
18
|
+
const id = handle.id
|
|
19
|
+
const root = handle.root
|
|
20
|
+
|
|
21
|
+
if (rest === '/context') {
|
|
22
|
+
if (req.method === 'GET') return json(readContext(root))
|
|
23
|
+
if (body.action === 'add') {
|
|
24
|
+
const item = addUserContext(root, body)
|
|
25
|
+
notify({ type: 'comments', domainId: id })
|
|
26
|
+
return json(item)
|
|
27
|
+
}
|
|
28
|
+
if (body.action === 'update') {
|
|
29
|
+
const item = updateContext(root, body.id, body)
|
|
30
|
+
return item ? json(item) : notFound()
|
|
31
|
+
}
|
|
32
|
+
if (body.action === 'delete') return json({ ok: deleteContext(root, body.id) })
|
|
33
|
+
if (body.action === 'include') {
|
|
34
|
+
const item = setAutoInclude(root, body.id, body.include)
|
|
35
|
+
return item ? json(item) : notFound()
|
|
36
|
+
}
|
|
37
|
+
return badRequest('unknown context action')
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (rest === '/copy-payload' && req.method === 'POST') {
|
|
41
|
+
await refreshAuto(handle)
|
|
42
|
+
const bundle = await getBundle(id)
|
|
43
|
+
const storedContext = readContext(root)
|
|
44
|
+
const openComments = readComments(root).comments.filter((comment) => comment.status === 'open')
|
|
45
|
+
const markdown = buildCopyMarkdown({
|
|
46
|
+
origin: bundle?.overlay.origin ?? id,
|
|
47
|
+
root,
|
|
48
|
+
renderFingerprint: bundle?.renderFingerprint ?? '',
|
|
49
|
+
schemaRevision: bundle?.schemaRevision,
|
|
50
|
+
openComments,
|
|
51
|
+
userContext: storedContext.user,
|
|
52
|
+
autoContext: body.includeAuto
|
|
53
|
+
? storedContext.auto.filter((item) => item.includeInHandoff)
|
|
54
|
+
: [],
|
|
55
|
+
documents: listDocuments(root),
|
|
56
|
+
})
|
|
57
|
+
return json({ markdown, openComments: openComments.length })
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return null
|
|
61
|
+
}
|