reigncode-app 1.3.2
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/AGENTS.md +30 -0
- package/Dockerfile +21 -0
- package/README.md +51 -0
- package/bunfig.toml +3 -0
- package/create-effect-simplification-spec.md +515 -0
- package/e2e/AGENTS.md +226 -0
- package/e2e/actions.ts +1018 -0
- package/e2e/app/home.spec.ts +24 -0
- package/e2e/app/navigation.spec.ts +10 -0
- package/e2e/app/palette.spec.ts +20 -0
- package/e2e/app/server-default.spec.ts +58 -0
- package/e2e/app/session.spec.ts +16 -0
- package/e2e/app/titlebar-history.spec.ts +120 -0
- package/e2e/commands/input-focus.spec.ts +15 -0
- package/e2e/commands/panels.spec.ts +33 -0
- package/e2e/commands/tab-close.spec.ts +32 -0
- package/e2e/files/file-open.spec.ts +31 -0
- package/e2e/files/file-tree.spec.ts +56 -0
- package/e2e/files/file-viewer.spec.ts +156 -0
- package/e2e/fixtures.ts +154 -0
- package/e2e/models/model-picker.spec.ts +48 -0
- package/e2e/models/models-visibility.spec.ts +61 -0
- package/e2e/projects/project-edit.spec.ts +43 -0
- package/e2e/projects/projects-close.spec.ts +54 -0
- package/e2e/projects/projects-switch.spec.ts +116 -0
- package/e2e/projects/workspace-new-session.spec.ts +94 -0
- package/e2e/projects/workspaces.spec.ts +375 -0
- package/e2e/prompt/context.spec.ts +95 -0
- package/e2e/prompt/prompt-async.spec.ts +76 -0
- package/e2e/prompt/prompt-drop-file-uri.spec.ts +22 -0
- package/e2e/prompt/prompt-drop-file.spec.ts +30 -0
- package/e2e/prompt/prompt-history.spec.ts +184 -0
- package/e2e/prompt/prompt-mention.spec.ts +26 -0
- package/e2e/prompt/prompt-multiline.spec.ts +24 -0
- package/e2e/prompt/prompt-shell.spec.ts +62 -0
- package/e2e/prompt/prompt-slash-open.spec.ts +22 -0
- package/e2e/prompt/prompt-slash-share.spec.ts +64 -0
- package/e2e/prompt/prompt-slash-terminal.spec.ts +18 -0
- package/e2e/prompt/prompt.spec.ts +55 -0
- package/e2e/selectors.ts +75 -0
- package/e2e/session/session-child-navigation.spec.ts +37 -0
- package/e2e/session/session-composer-dock.spec.ts +530 -0
- package/e2e/session/session-model-persistence.spec.ts +359 -0
- package/e2e/session/session-review.spec.ts +426 -0
- package/e2e/session/session-undo-redo.spec.ts +233 -0
- package/e2e/session/session.spec.ts +174 -0
- package/e2e/settings/settings-keybinds.spec.ts +389 -0
- package/e2e/settings/settings-models.spec.ts +122 -0
- package/e2e/settings/settings-providers.spec.ts +136 -0
- package/e2e/settings/settings.spec.ts +519 -0
- package/e2e/sidebar/sidebar-popover-actions.spec.ts +118 -0
- package/e2e/sidebar/sidebar-session-links.spec.ts +30 -0
- package/e2e/sidebar/sidebar.spec.ts +40 -0
- package/e2e/status/status-popover.spec.ts +94 -0
- package/e2e/terminal/terminal-init.spec.ts +28 -0
- package/e2e/terminal/terminal-reconnect.spec.ts +46 -0
- package/e2e/terminal/terminal-tabs.spec.ts +168 -0
- package/e2e/terminal/terminal.spec.ts +18 -0
- package/e2e/thinking-level.spec.ts +25 -0
- package/e2e/tsconfig.json +9 -0
- package/e2e/utils.ts +63 -0
- package/happydom.ts +75 -0
- package/index.html +23 -0
- package/package.json +77 -0
- package/playwright.config.ts +45 -0
- package/public/_headers +17 -0
- package/public/oc-theme-preload.js +35 -0
- package/script/e2e-local.ts +180 -0
- package/src/addons/serialize.test.ts +319 -0
- package/src/addons/serialize.ts +634 -0
- package/src/app.tsx +308 -0
- package/src/components/debug-bar.tsx +443 -0
- package/src/components/dialog-connect-provider.tsx +617 -0
- package/src/components/dialog-custom-provider-form.ts +158 -0
- package/src/components/dialog-custom-provider.test.ts +80 -0
- package/src/components/dialog-custom-provider.tsx +329 -0
- package/src/components/dialog-edit-project.tsx +255 -0
- package/src/components/dialog-fork.tsx +108 -0
- package/src/components/dialog-manage-models.tsx +101 -0
- package/src/components/dialog-release-notes.tsx +144 -0
- package/src/components/dialog-select-directory.tsx +392 -0
- package/src/components/dialog-select-file.tsx +466 -0
- package/src/components/dialog-select-mcp.tsx +107 -0
- package/src/components/dialog-select-model-unpaid.tsx +137 -0
- package/src/components/dialog-select-model.tsx +220 -0
- package/src/components/dialog-select-provider.tsx +86 -0
- package/src/components/dialog-select-server.tsx +649 -0
- package/src/components/dialog-settings.tsx +73 -0
- package/src/components/file-tree.test.ts +78 -0
- package/src/components/file-tree.tsx +507 -0
- package/src/components/link.tsx +26 -0
- package/src/components/model-tooltip.tsx +91 -0
- package/src/components/prompt-input/attachments.test.ts +44 -0
- package/src/components/prompt-input/attachments.ts +201 -0
- package/src/components/prompt-input/build-request-parts.test.ts +312 -0
- package/src/components/prompt-input/build-request-parts.ts +175 -0
- package/src/components/prompt-input/context-items.tsx +88 -0
- package/src/components/prompt-input/drag-overlay.tsx +25 -0
- package/src/components/prompt-input/editor-dom.test.ts +99 -0
- package/src/components/prompt-input/editor-dom.ts +148 -0
- package/src/components/prompt-input/files.ts +66 -0
- package/src/components/prompt-input/history.test.ts +153 -0
- package/src/components/prompt-input/history.ts +256 -0
- package/src/components/prompt-input/image-attachments.tsx +58 -0
- package/src/components/prompt-input/paste.ts +24 -0
- package/src/components/prompt-input/placeholder.test.ts +48 -0
- package/src/components/prompt-input/placeholder.ts +15 -0
- package/src/components/prompt-input/slash-popover.tsx +141 -0
- package/src/components/prompt-input/submit.test.ts +346 -0
- package/src/components/prompt-input/submit.ts +579 -0
- package/src/components/prompt-input.tsx +1595 -0
- package/src/components/server/server-row.tsx +130 -0
- package/src/components/session/index.ts +5 -0
- package/src/components/session/session-context-breakdown.test.ts +61 -0
- package/src/components/session/session-context-breakdown.ts +132 -0
- package/src/components/session/session-context-format.ts +20 -0
- package/src/components/session/session-context-metrics.test.ts +101 -0
- package/src/components/session/session-context-metrics.ts +82 -0
- package/src/components/session/session-context-tab.tsx +339 -0
- package/src/components/session/session-header.tsx +486 -0
- package/src/components/session/session-new-view.tsx +91 -0
- package/src/components/session/session-sortable-tab.tsx +70 -0
- package/src/components/session/session-sortable-terminal-tab.tsx +193 -0
- package/src/components/session-context-usage.tsx +122 -0
- package/src/components/settings-general.tsx +585 -0
- package/src/components/settings-keybinds.tsx +453 -0
- package/src/components/settings-list.tsx +5 -0
- package/src/components/settings-models.tsx +137 -0
- package/src/components/settings-providers.tsx +251 -0
- package/src/components/status-popover.tsx +419 -0
- package/src/components/terminal.tsx +653 -0
- package/src/components/titlebar-history.test.ts +63 -0
- package/src/components/titlebar-history.ts +57 -0
- package/src/components/titlebar.tsx +312 -0
- package/src/constants/file-picker.ts +89 -0
- package/src/context/command-keybind.test.ts +69 -0
- package/src/context/command.test.ts +25 -0
- package/src/context/command.tsx +437 -0
- package/src/context/comments.test.ts +186 -0
- package/src/context/comments.tsx +243 -0
- package/src/context/file/content-cache.ts +88 -0
- package/src/context/file/path.test.ts +360 -0
- package/src/context/file/path.ts +151 -0
- package/src/context/file/tree-store.ts +170 -0
- package/src/context/file/types.ts +41 -0
- package/src/context/file/view-cache.ts +146 -0
- package/src/context/file/watcher.test.ts +149 -0
- package/src/context/file/watcher.ts +53 -0
- package/src/context/file-content-eviction-accounting.test.ts +65 -0
- package/src/context/file.tsx +280 -0
- package/src/context/global-sdk.tsx +232 -0
- package/src/context/global-sync/bootstrap.ts +206 -0
- package/src/context/global-sync/child-store.test.ts +38 -0
- package/src/context/global-sync/child-store.ts +281 -0
- package/src/context/global-sync/event-reducer.test.ts +552 -0
- package/src/context/global-sync/event-reducer.ts +359 -0
- package/src/context/global-sync/eviction.ts +28 -0
- package/src/context/global-sync/queue.ts +83 -0
- package/src/context/global-sync/session-cache.test.ts +102 -0
- package/src/context/global-sync/session-cache.ts +62 -0
- package/src/context/global-sync/session-load.ts +25 -0
- package/src/context/global-sync/session-prefetch.test.ts +96 -0
- package/src/context/global-sync/session-prefetch.ts +100 -0
- package/src/context/global-sync/session-trim.test.ts +59 -0
- package/src/context/global-sync/session-trim.ts +56 -0
- package/src/context/global-sync/types.ts +133 -0
- package/src/context/global-sync/utils.ts +25 -0
- package/src/context/global-sync.test.ts +122 -0
- package/src/context/global-sync.tsx +408 -0
- package/src/context/highlights.tsx +233 -0
- package/src/context/language.tsx +248 -0
- package/src/context/layout-scroll.test.ts +64 -0
- package/src/context/layout-scroll.ts +126 -0
- package/src/context/layout.test.ts +69 -0
- package/src/context/layout.tsx +937 -0
- package/src/context/local.tsx +422 -0
- package/src/context/model-variant.test.ts +86 -0
- package/src/context/model-variant.ts +52 -0
- package/src/context/models.tsx +163 -0
- package/src/context/notification.tsx +373 -0
- package/src/context/permission-auto-respond.test.ts +102 -0
- package/src/context/permission-auto-respond.ts +51 -0
- package/src/context/permission.tsx +277 -0
- package/src/context/platform.tsx +99 -0
- package/src/context/prompt.tsx +297 -0
- package/src/context/sdk.tsx +49 -0
- package/src/context/server.tsx +295 -0
- package/src/context/settings.tsx +241 -0
- package/src/context/sync-optimistic.test.ts +123 -0
- package/src/context/sync.tsx +618 -0
- package/src/context/terminal-title.ts +51 -0
- package/src/context/terminal.test.ts +82 -0
- package/src/context/terminal.tsx +437 -0
- package/src/entry.tsx +144 -0
- package/src/env.d.ts +18 -0
- package/src/hooks/use-providers.ts +44 -0
- package/src/i18n/ar.ts +855 -0
- package/src/i18n/br.ts +867 -0
- package/src/i18n/bs.ts +943 -0
- package/src/i18n/da.ts +937 -0
- package/src/i18n/de.ts +879 -0
- package/src/i18n/en.ts +948 -0
- package/src/i18n/es.ts +950 -0
- package/src/i18n/fr.ts +878 -0
- package/src/i18n/ja.ts +861 -0
- package/src/i18n/ko.ts +860 -0
- package/src/i18n/no.ts +944 -0
- package/src/i18n/parity.test.ts +32 -0
- package/src/i18n/pl.ts +865 -0
- package/src/i18n/ru.ts +946 -0
- package/src/i18n/th.ts +933 -0
- package/src/i18n/tr.ts +952 -0
- package/src/i18n/zh.ts +930 -0
- package/src/i18n/zht.ts +925 -0
- package/src/index.css +29 -0
- package/src/index.ts +6 -0
- package/src/pages/directory-layout.tsx +88 -0
- package/src/pages/error.tsx +327 -0
- package/src/pages/home.tsx +131 -0
- package/src/pages/layout/deep-links.ts +50 -0
- package/src/pages/layout/helpers.test.ts +211 -0
- package/src/pages/layout/helpers.ts +98 -0
- package/src/pages/layout/inline-editor.tsx +126 -0
- package/src/pages/layout/sidebar-items.tsx +437 -0
- package/src/pages/layout/sidebar-project.tsx +384 -0
- package/src/pages/layout/sidebar-shell.tsx +125 -0
- package/src/pages/layout/sidebar-workspace.tsx +504 -0
- package/src/pages/layout.tsx +2509 -0
- package/src/pages/session/composer/index.ts +2 -0
- package/src/pages/session/composer/session-composer-region.tsx +255 -0
- package/src/pages/session/composer/session-composer-state.test.ts +128 -0
- package/src/pages/session/composer/session-composer-state.ts +249 -0
- package/src/pages/session/composer/session-followup-dock.tsx +109 -0
- package/src/pages/session/composer/session-permission-dock.tsx +74 -0
- package/src/pages/session/composer/session-question-dock.tsx +449 -0
- package/src/pages/session/composer/session-request-tree.ts +52 -0
- package/src/pages/session/composer/session-revert-dock.tsx +99 -0
- package/src/pages/session/composer/session-todo-dock.tsx +330 -0
- package/src/pages/session/file-tab-scroll.test.ts +40 -0
- package/src/pages/session/file-tab-scroll.ts +67 -0
- package/src/pages/session/file-tabs.tsx +456 -0
- package/src/pages/session/handoff.ts +36 -0
- package/src/pages/session/helpers.test.ts +181 -0
- package/src/pages/session/helpers.ts +198 -0
- package/src/pages/session/message-gesture.test.ts +62 -0
- package/src/pages/session/message-gesture.ts +21 -0
- package/src/pages/session/message-id-from-hash.ts +6 -0
- package/src/pages/session/message-timeline.tsx +1013 -0
- package/src/pages/session/review-tab.tsx +170 -0
- package/src/pages/session/session-layout.ts +20 -0
- package/src/pages/session/session-model-helpers.test.ts +51 -0
- package/src/pages/session/session-model-helpers.ts +16 -0
- package/src/pages/session/session-side-panel.tsx +453 -0
- package/src/pages/session/terminal-label.ts +16 -0
- package/src/pages/session/terminal-panel.test.ts +25 -0
- package/src/pages/session/terminal-panel.tsx +326 -0
- package/src/pages/session/use-session-commands.tsx +495 -0
- package/src/pages/session/use-session-hash-scroll.test.ts +16 -0
- package/src/pages/session/use-session-hash-scroll.ts +197 -0
- package/src/pages/session.tsx +1841 -0
- package/src/sst-env.d.ts +12 -0
- package/src/testing/model-selection.ts +80 -0
- package/src/testing/prompt.ts +56 -0
- package/src/testing/session-composer.ts +84 -0
- package/src/testing/terminal.ts +118 -0
- package/src/theme-preload.test.ts +46 -0
- package/src/utils/agent.ts +23 -0
- package/src/utils/aim.ts +138 -0
- package/src/utils/base64.ts +10 -0
- package/src/utils/comment-note.ts +88 -0
- package/src/utils/id.ts +99 -0
- package/src/utils/notification-click.test.ts +27 -0
- package/src/utils/notification-click.ts +13 -0
- package/src/utils/persist.test.ts +115 -0
- package/src/utils/persist.ts +476 -0
- package/src/utils/prompt.test.ts +44 -0
- package/src/utils/prompt.ts +203 -0
- package/src/utils/runtime-adapters.test.ts +62 -0
- package/src/utils/runtime-adapters.ts +39 -0
- package/src/utils/same.ts +6 -0
- package/src/utils/scoped-cache.test.ts +69 -0
- package/src/utils/scoped-cache.ts +104 -0
- package/src/utils/server-errors.test.ts +131 -0
- package/src/utils/server-errors.ts +80 -0
- package/src/utils/server-health.test.ts +123 -0
- package/src/utils/server-health.ts +91 -0
- package/src/utils/server.ts +22 -0
- package/src/utils/solid-dnd.tsx +49 -0
- package/src/utils/sound.ts +117 -0
- package/src/utils/terminal-writer.test.ts +64 -0
- package/src/utils/terminal-writer.ts +65 -0
- package/src/utils/time.ts +22 -0
- package/src/utils/uuid.test.ts +78 -0
- package/src/utils/uuid.ts +12 -0
- package/src/utils/worktree.test.ts +46 -0
- package/src/utils/worktree.ts +73 -0
- package/sst-env.d.ts +10 -0
- package/tsconfig.json +26 -0
- package/vite.config.ts +15 -0
- package/vite.js +26 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import { createEffect, createMemo, onCleanup } from "solid-js"
|
|
2
|
+
import { createStore, produce } from "solid-js/store"
|
|
3
|
+
import { createSimpleContext } from "@reign-labs/ui/context"
|
|
4
|
+
import type { PermissionRequest } from "@reign-labs/sdk/v2/client"
|
|
5
|
+
import { Persist, persisted } from "@/utils/persist"
|
|
6
|
+
import { useGlobalSDK } from "@/context/global-sdk"
|
|
7
|
+
import { useGlobalSync } from "./global-sync"
|
|
8
|
+
import { useParams } from "@solidjs/router"
|
|
9
|
+
import { decode64 } from "@/utils/base64"
|
|
10
|
+
import {
|
|
11
|
+
acceptKey,
|
|
12
|
+
directoryAcceptKey,
|
|
13
|
+
isDirectoryAutoAccepting,
|
|
14
|
+
autoRespondsPermission,
|
|
15
|
+
} from "./permission-auto-respond"
|
|
16
|
+
|
|
17
|
+
type PermissionRespondFn = (input: {
|
|
18
|
+
sessionID: string
|
|
19
|
+
permissionID: string
|
|
20
|
+
response: "once" | "always" | "reject"
|
|
21
|
+
directory?: string
|
|
22
|
+
}) => void
|
|
23
|
+
|
|
24
|
+
function isNonAllowRule(rule: unknown) {
|
|
25
|
+
if (!rule) return false
|
|
26
|
+
if (typeof rule === "string") return rule !== "allow"
|
|
27
|
+
if (typeof rule !== "object") return false
|
|
28
|
+
if (Array.isArray(rule)) return false
|
|
29
|
+
|
|
30
|
+
for (const action of Object.values(rule)) {
|
|
31
|
+
if (action !== "allow") return true
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return false
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function hasPermissionPromptRules(permission: unknown) {
|
|
38
|
+
if (!permission) return false
|
|
39
|
+
if (typeof permission === "string") return permission !== "allow"
|
|
40
|
+
if (typeof permission !== "object") return false
|
|
41
|
+
if (Array.isArray(permission)) return false
|
|
42
|
+
|
|
43
|
+
const config = permission as Record<string, unknown>
|
|
44
|
+
return Object.values(config).some(isNonAllowRule)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export const { use: usePermission, provider: PermissionProvider } = createSimpleContext({
|
|
48
|
+
name: "Permission",
|
|
49
|
+
init: () => {
|
|
50
|
+
const params = useParams()
|
|
51
|
+
const globalSDK = useGlobalSDK()
|
|
52
|
+
const globalSync = useGlobalSync()
|
|
53
|
+
|
|
54
|
+
const permissionsEnabled = createMemo(() => {
|
|
55
|
+
const directory = decode64(params.dir)
|
|
56
|
+
if (!directory) return false
|
|
57
|
+
const [store] = globalSync.child(directory)
|
|
58
|
+
return hasPermissionPromptRules(store.config.permission)
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const [store, setStore, _, ready] = persisted(
|
|
62
|
+
{
|
|
63
|
+
...Persist.global("permission", ["permission.v3"]),
|
|
64
|
+
migrate(value) {
|
|
65
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return value
|
|
66
|
+
|
|
67
|
+
const data = value as Record<string, unknown>
|
|
68
|
+
if (data.autoAccept) return value
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
...data,
|
|
72
|
+
autoAccept:
|
|
73
|
+
typeof data.autoAcceptEdits === "object" && data.autoAcceptEdits && !Array.isArray(data.autoAcceptEdits)
|
|
74
|
+
? data.autoAcceptEdits
|
|
75
|
+
: {},
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
createStore({
|
|
80
|
+
autoAccept: {} as Record<string, boolean>,
|
|
81
|
+
}),
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
// When config has permission: "allow", auto-enable directory-level auto-accept
|
|
85
|
+
createEffect(() => {
|
|
86
|
+
if (!ready()) return
|
|
87
|
+
const directory = decode64(params.dir)
|
|
88
|
+
if (!directory) return
|
|
89
|
+
const [childStore] = globalSync.child(directory)
|
|
90
|
+
const perm = childStore.config.permission
|
|
91
|
+
if (typeof perm === "string" && perm === "allow") {
|
|
92
|
+
const key = directoryAcceptKey(directory)
|
|
93
|
+
if (store.autoAccept[key] === undefined) {
|
|
94
|
+
setStore(
|
|
95
|
+
produce((draft) => {
|
|
96
|
+
draft.autoAccept[key] = true
|
|
97
|
+
}),
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const MAX_RESPONDED = 1000
|
|
104
|
+
const RESPONDED_TTL_MS = 60 * 60 * 1000
|
|
105
|
+
const responded = new Map<string, number>()
|
|
106
|
+
const enableVersion = new Map<string, number>()
|
|
107
|
+
|
|
108
|
+
function pruneResponded(now: number) {
|
|
109
|
+
for (const [id, ts] of responded) {
|
|
110
|
+
if (now - ts < RESPONDED_TTL_MS) break
|
|
111
|
+
responded.delete(id)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
for (const id of responded.keys()) {
|
|
115
|
+
if (responded.size <= MAX_RESPONDED) break
|
|
116
|
+
responded.delete(id)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const respond: PermissionRespondFn = (input) => {
|
|
121
|
+
globalSDK.client.permission.respond(input).catch(() => {
|
|
122
|
+
responded.delete(input.permissionID)
|
|
123
|
+
})
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function respondOnce(permission: PermissionRequest, directory?: string) {
|
|
127
|
+
const now = Date.now()
|
|
128
|
+
const hit = responded.has(permission.id)
|
|
129
|
+
responded.delete(permission.id)
|
|
130
|
+
responded.set(permission.id, now)
|
|
131
|
+
pruneResponded(now)
|
|
132
|
+
if (hit) return
|
|
133
|
+
respond({
|
|
134
|
+
sessionID: permission.sessionID,
|
|
135
|
+
permissionID: permission.id,
|
|
136
|
+
response: "once",
|
|
137
|
+
directory,
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function isAutoAccepting(sessionID: string, directory?: string) {
|
|
142
|
+
const session = directory ? globalSync.child(directory, { bootstrap: false })[0].session : []
|
|
143
|
+
return autoRespondsPermission(store.autoAccept, session, { sessionID }, directory)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function isAutoAcceptingDirectory(directory: string) {
|
|
147
|
+
return isDirectoryAutoAccepting(store.autoAccept, directory)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function shouldAutoRespond(permission: PermissionRequest, directory?: string) {
|
|
151
|
+
const session = directory ? globalSync.child(directory, { bootstrap: false })[0].session : []
|
|
152
|
+
return autoRespondsPermission(store.autoAccept, session, permission, directory)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function bumpEnableVersion(sessionID: string, directory?: string) {
|
|
156
|
+
const key = acceptKey(sessionID, directory)
|
|
157
|
+
const next = (enableVersion.get(key) ?? 0) + 1
|
|
158
|
+
enableVersion.set(key, next)
|
|
159
|
+
return next
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const unsubscribe = globalSDK.event.listen((e) => {
|
|
163
|
+
const event = e.details
|
|
164
|
+
if (event?.type !== "permission.asked") return
|
|
165
|
+
|
|
166
|
+
const perm = event.properties
|
|
167
|
+
if (!shouldAutoRespond(perm, e.name)) return
|
|
168
|
+
|
|
169
|
+
respondOnce(perm, e.name)
|
|
170
|
+
})
|
|
171
|
+
onCleanup(unsubscribe)
|
|
172
|
+
|
|
173
|
+
function enableDirectory(directory: string) {
|
|
174
|
+
const key = directoryAcceptKey(directory)
|
|
175
|
+
setStore(
|
|
176
|
+
produce((draft) => {
|
|
177
|
+
draft.autoAccept[key] = true
|
|
178
|
+
}),
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
globalSDK.client.permission
|
|
182
|
+
.list({ directory })
|
|
183
|
+
.then((x) => {
|
|
184
|
+
if (!isAutoAcceptingDirectory(directory)) return
|
|
185
|
+
for (const perm of x.data ?? []) {
|
|
186
|
+
if (!perm?.id) continue
|
|
187
|
+
if (!shouldAutoRespond(perm, directory)) continue
|
|
188
|
+
respondOnce(perm, directory)
|
|
189
|
+
}
|
|
190
|
+
})
|
|
191
|
+
.catch(() => undefined)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function disableDirectory(directory: string) {
|
|
195
|
+
const key = directoryAcceptKey(directory)
|
|
196
|
+
setStore(
|
|
197
|
+
produce((draft) => {
|
|
198
|
+
draft.autoAccept[key] = false
|
|
199
|
+
}),
|
|
200
|
+
)
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function enable(sessionID: string, directory: string) {
|
|
204
|
+
const key = acceptKey(sessionID, directory)
|
|
205
|
+
const version = bumpEnableVersion(sessionID, directory)
|
|
206
|
+
setStore(
|
|
207
|
+
produce((draft) => {
|
|
208
|
+
draft.autoAccept[key] = true
|
|
209
|
+
delete draft.autoAccept[sessionID]
|
|
210
|
+
}),
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
globalSDK.client.permission
|
|
214
|
+
.list({ directory })
|
|
215
|
+
.then((x) => {
|
|
216
|
+
if (enableVersion.get(key) !== version) return
|
|
217
|
+
if (!isAutoAccepting(sessionID, directory)) return
|
|
218
|
+
for (const perm of x.data ?? []) {
|
|
219
|
+
if (!perm?.id) continue
|
|
220
|
+
if (!shouldAutoRespond(perm, directory)) continue
|
|
221
|
+
respondOnce(perm, directory)
|
|
222
|
+
}
|
|
223
|
+
})
|
|
224
|
+
.catch(() => undefined)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function disable(sessionID: string, directory?: string) {
|
|
228
|
+
bumpEnableVersion(sessionID, directory)
|
|
229
|
+
const key = directory ? acceptKey(sessionID, directory) : sessionID
|
|
230
|
+
setStore(
|
|
231
|
+
produce((draft) => {
|
|
232
|
+
draft.autoAccept[key] = false
|
|
233
|
+
if (!directory) return
|
|
234
|
+
delete draft.autoAccept[sessionID]
|
|
235
|
+
}),
|
|
236
|
+
)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
ready,
|
|
241
|
+
respond,
|
|
242
|
+
autoResponds(permission: PermissionRequest, directory?: string) {
|
|
243
|
+
return shouldAutoRespond(permission, directory)
|
|
244
|
+
},
|
|
245
|
+
isAutoAccepting,
|
|
246
|
+
isAutoAcceptingDirectory,
|
|
247
|
+
toggleAutoAccept(sessionID: string, directory: string) {
|
|
248
|
+
if (isAutoAccepting(sessionID, directory)) {
|
|
249
|
+
disable(sessionID, directory)
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
enable(sessionID, directory)
|
|
254
|
+
},
|
|
255
|
+
toggleAutoAcceptDirectory(directory: string) {
|
|
256
|
+
if (isAutoAcceptingDirectory(directory)) {
|
|
257
|
+
disableDirectory(directory)
|
|
258
|
+
return
|
|
259
|
+
}
|
|
260
|
+
enableDirectory(directory)
|
|
261
|
+
},
|
|
262
|
+
enableAutoAccept(sessionID: string, directory: string) {
|
|
263
|
+
if (isAutoAccepting(sessionID, directory)) return
|
|
264
|
+
enable(sessionID, directory)
|
|
265
|
+
},
|
|
266
|
+
disableAutoAccept(sessionID: string, directory?: string) {
|
|
267
|
+
disable(sessionID, directory)
|
|
268
|
+
},
|
|
269
|
+
permissionsEnabled,
|
|
270
|
+
isPermissionAllowAll(directory: string) {
|
|
271
|
+
const [childStore] = globalSync.child(directory)
|
|
272
|
+
const perm = childStore.config.permission
|
|
273
|
+
return typeof perm === "string" && perm === "allow"
|
|
274
|
+
},
|
|
275
|
+
}
|
|
276
|
+
},
|
|
277
|
+
})
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { createSimpleContext } from "@reign-labs/ui/context"
|
|
2
|
+
import type { AsyncStorage, SyncStorage } from "@solid-primitives/storage"
|
|
3
|
+
import type { Accessor } from "solid-js"
|
|
4
|
+
import { ServerConnection } from "./server"
|
|
5
|
+
|
|
6
|
+
type PickerPaths = string | string[] | null
|
|
7
|
+
type OpenDirectoryPickerOptions = { title?: string; multiple?: boolean }
|
|
8
|
+
type OpenFilePickerOptions = { title?: string; multiple?: boolean; accept?: string[]; extensions?: string[] }
|
|
9
|
+
type SaveFilePickerOptions = { title?: string; defaultPath?: string }
|
|
10
|
+
type UpdateInfo = { updateAvailable: boolean; version?: string }
|
|
11
|
+
|
|
12
|
+
export type Platform = {
|
|
13
|
+
/** Platform discriminator */
|
|
14
|
+
platform: "web" | "desktop"
|
|
15
|
+
|
|
16
|
+
/** Desktop OS (Tauri only) */
|
|
17
|
+
os?: "macos" | "windows" | "linux"
|
|
18
|
+
|
|
19
|
+
/** App version */
|
|
20
|
+
version?: string
|
|
21
|
+
|
|
22
|
+
/** Open a URL in the default browser */
|
|
23
|
+
openLink(url: string): void
|
|
24
|
+
|
|
25
|
+
/** Open a local path in a local app (desktop only) */
|
|
26
|
+
openPath?(path: string, app?: string): Promise<void>
|
|
27
|
+
|
|
28
|
+
/** Restart the app */
|
|
29
|
+
restart(): Promise<void>
|
|
30
|
+
|
|
31
|
+
/** Navigate back in history */
|
|
32
|
+
back(): void
|
|
33
|
+
|
|
34
|
+
/** Navigate forward in history */
|
|
35
|
+
forward(): void
|
|
36
|
+
|
|
37
|
+
/** Send a system notification (optional deep link) */
|
|
38
|
+
notify(title: string, description?: string, href?: string): Promise<void>
|
|
39
|
+
|
|
40
|
+
/** Open directory picker dialog (native on Tauri, server-backed on web) */
|
|
41
|
+
openDirectoryPickerDialog?(opts?: OpenDirectoryPickerOptions): Promise<PickerPaths>
|
|
42
|
+
|
|
43
|
+
/** Open native file picker dialog (Tauri only) */
|
|
44
|
+
openFilePickerDialog?(opts?: OpenFilePickerOptions): Promise<PickerPaths>
|
|
45
|
+
|
|
46
|
+
/** Save file picker dialog (Tauri only) */
|
|
47
|
+
saveFilePickerDialog?(opts?: SaveFilePickerOptions): Promise<string | null>
|
|
48
|
+
|
|
49
|
+
/** Storage mechanism, defaults to localStorage */
|
|
50
|
+
storage?: (name?: string) => SyncStorage | AsyncStorage
|
|
51
|
+
|
|
52
|
+
/** Check for updates (Tauri only) */
|
|
53
|
+
checkUpdate?(): Promise<UpdateInfo>
|
|
54
|
+
|
|
55
|
+
/** Install updates (Tauri only) */
|
|
56
|
+
update?(): Promise<void>
|
|
57
|
+
|
|
58
|
+
/** Fetch override */
|
|
59
|
+
fetch?: typeof fetch
|
|
60
|
+
|
|
61
|
+
/** Get the configured default server URL (platform-specific) */
|
|
62
|
+
getDefaultServer?(): Promise<ServerConnection.Key | null>
|
|
63
|
+
|
|
64
|
+
/** Set the default server URL to use on app startup (platform-specific) */
|
|
65
|
+
setDefaultServer?(url: ServerConnection.Key | null): Promise<void> | void
|
|
66
|
+
|
|
67
|
+
/** Get the configured WSL integration (desktop only) */
|
|
68
|
+
getWslEnabled?(): Promise<boolean>
|
|
69
|
+
|
|
70
|
+
/** Set the configured WSL integration (desktop only) */
|
|
71
|
+
setWslEnabled?(config: boolean): Promise<void> | void
|
|
72
|
+
|
|
73
|
+
/** Get the preferred display backend (desktop only) */
|
|
74
|
+
getDisplayBackend?(): Promise<DisplayBackend | null> | DisplayBackend | null
|
|
75
|
+
|
|
76
|
+
/** Set the preferred display backend (desktop only) */
|
|
77
|
+
setDisplayBackend?(backend: DisplayBackend): Promise<void>
|
|
78
|
+
|
|
79
|
+
/** Parse markdown to HTML using native parser (desktop only, returns unprocessed code blocks) */
|
|
80
|
+
parseMarkdown?(markdown: string): Promise<string>
|
|
81
|
+
|
|
82
|
+
/** Webview zoom level (desktop only) */
|
|
83
|
+
webviewZoom?: Accessor<number>
|
|
84
|
+
|
|
85
|
+
/** Check if an editor app exists (desktop only) */
|
|
86
|
+
checkAppExists?(appName: string): Promise<boolean>
|
|
87
|
+
|
|
88
|
+
/** Read image from clipboard (desktop only) */
|
|
89
|
+
readClipboardImage?(): Promise<File | null>
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export type DisplayBackend = "auto" | "wayland"
|
|
93
|
+
|
|
94
|
+
export const { use: usePlatform, provider: PlatformProvider } = createSimpleContext({
|
|
95
|
+
name: "Platform",
|
|
96
|
+
init: (props: { value: Platform }) => {
|
|
97
|
+
return props.value
|
|
98
|
+
},
|
|
99
|
+
})
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import { createSimpleContext } from "@reign-labs/ui/context"
|
|
2
|
+
import { checksum } from "@reign-labs/util/encode"
|
|
3
|
+
import { useParams } from "@solidjs/router"
|
|
4
|
+
import { batch, createMemo, createRoot, getOwner, onCleanup } from "solid-js"
|
|
5
|
+
import { createStore, type SetStoreFunction } from "solid-js/store"
|
|
6
|
+
import type { FileSelection } from "@/context/file"
|
|
7
|
+
import { Persist, persisted } from "@/utils/persist"
|
|
8
|
+
|
|
9
|
+
interface PartBase {
|
|
10
|
+
content: string
|
|
11
|
+
start: number
|
|
12
|
+
end: number
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface TextPart extends PartBase {
|
|
16
|
+
type: "text"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface FileAttachmentPart extends PartBase {
|
|
20
|
+
type: "file"
|
|
21
|
+
path: string
|
|
22
|
+
selection?: FileSelection
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AgentPart extends PartBase {
|
|
26
|
+
type: "agent"
|
|
27
|
+
name: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ImageAttachmentPart {
|
|
31
|
+
type: "image"
|
|
32
|
+
id: string
|
|
33
|
+
filename: string
|
|
34
|
+
mime: string
|
|
35
|
+
dataUrl: string
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type ContentPart = TextPart | FileAttachmentPart | AgentPart | ImageAttachmentPart
|
|
39
|
+
export type Prompt = ContentPart[]
|
|
40
|
+
|
|
41
|
+
export type FileContextItem = {
|
|
42
|
+
type: "file"
|
|
43
|
+
path: string
|
|
44
|
+
selection?: FileSelection
|
|
45
|
+
comment?: string
|
|
46
|
+
commentID?: string
|
|
47
|
+
commentOrigin?: "review" | "file"
|
|
48
|
+
preview?: string
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type ContextItem = FileContextItem
|
|
52
|
+
|
|
53
|
+
export const DEFAULT_PROMPT: Prompt = [{ type: "text", content: "", start: 0, end: 0 }]
|
|
54
|
+
|
|
55
|
+
function isSelectionEqual(a?: FileSelection, b?: FileSelection) {
|
|
56
|
+
if (!a && !b) return true
|
|
57
|
+
if (!a || !b) return false
|
|
58
|
+
return (
|
|
59
|
+
a.startLine === b.startLine && a.startChar === b.startChar && a.endLine === b.endLine && a.endChar === b.endChar
|
|
60
|
+
)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isPartEqual(partA: ContentPart, partB: ContentPart) {
|
|
64
|
+
switch (partA.type) {
|
|
65
|
+
case "text":
|
|
66
|
+
return partB.type === "text" && partA.content === partB.content
|
|
67
|
+
case "file":
|
|
68
|
+
return partB.type === "file" && partA.path === partB.path && isSelectionEqual(partA.selection, partB.selection)
|
|
69
|
+
case "agent":
|
|
70
|
+
return partB.type === "agent" && partA.name === partB.name
|
|
71
|
+
case "image":
|
|
72
|
+
return partB.type === "image" && partA.id === partB.id
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function isPromptEqual(promptA: Prompt, promptB: Prompt): boolean {
|
|
77
|
+
if (promptA.length !== promptB.length) return false
|
|
78
|
+
for (let i = 0; i < promptA.length; i++) {
|
|
79
|
+
if (!isPartEqual(promptA[i], promptB[i])) return false
|
|
80
|
+
}
|
|
81
|
+
return true
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function cloneSelection(selection?: FileSelection) {
|
|
85
|
+
if (!selection) return undefined
|
|
86
|
+
return { ...selection }
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function clonePart(part: ContentPart): ContentPart {
|
|
90
|
+
if (part.type === "text") return { ...part }
|
|
91
|
+
if (part.type === "image") return { ...part }
|
|
92
|
+
if (part.type === "agent") return { ...part }
|
|
93
|
+
return {
|
|
94
|
+
...part,
|
|
95
|
+
selection: cloneSelection(part.selection),
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function clonePrompt(prompt: Prompt): Prompt {
|
|
100
|
+
return prompt.map(clonePart)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function contextItemKey(item: ContextItem) {
|
|
104
|
+
if (item.type !== "file") return item.type
|
|
105
|
+
const start = item.selection?.startLine
|
|
106
|
+
const end = item.selection?.endLine
|
|
107
|
+
const key = `${item.type}:${item.path}:${start}:${end}`
|
|
108
|
+
|
|
109
|
+
if (item.commentID) {
|
|
110
|
+
return `${key}:c=${item.commentID}`
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const comment = item.comment?.trim()
|
|
114
|
+
if (!comment) return key
|
|
115
|
+
const digest = checksum(comment) ?? comment
|
|
116
|
+
return `${key}:c=${digest.slice(0, 8)}`
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isCommentItem(item: ContextItem | (ContextItem & { key: string })) {
|
|
120
|
+
return item.type === "file" && !!item.comment?.trim()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function createPromptActions(
|
|
124
|
+
setStore: SetStoreFunction<{
|
|
125
|
+
prompt: Prompt
|
|
126
|
+
cursor?: number
|
|
127
|
+
context: {
|
|
128
|
+
items: (ContextItem & { key: string })[]
|
|
129
|
+
}
|
|
130
|
+
}>,
|
|
131
|
+
) {
|
|
132
|
+
return {
|
|
133
|
+
set(prompt: Prompt, cursorPosition?: number) {
|
|
134
|
+
const next = clonePrompt(prompt)
|
|
135
|
+
batch(() => {
|
|
136
|
+
setStore("prompt", next)
|
|
137
|
+
if (cursorPosition !== undefined) setStore("cursor", cursorPosition)
|
|
138
|
+
})
|
|
139
|
+
},
|
|
140
|
+
reset() {
|
|
141
|
+
batch(() => {
|
|
142
|
+
setStore("prompt", clonePrompt(DEFAULT_PROMPT))
|
|
143
|
+
setStore("cursor", 0)
|
|
144
|
+
})
|
|
145
|
+
},
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const WORKSPACE_KEY = "__workspace__"
|
|
150
|
+
const MAX_PROMPT_SESSIONS = 20
|
|
151
|
+
|
|
152
|
+
type PromptSession = ReturnType<typeof createPromptSession>
|
|
153
|
+
|
|
154
|
+
type Scope = {
|
|
155
|
+
dir: string
|
|
156
|
+
id?: string
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
type PromptCacheEntry = {
|
|
160
|
+
value: PromptSession
|
|
161
|
+
dispose: VoidFunction
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function createPromptSession(dir: string, id: string | undefined) {
|
|
165
|
+
const legacy = `${dir}/prompt${id ? "/" + id : ""}.v2`
|
|
166
|
+
|
|
167
|
+
const [store, setStore, _, ready] = persisted(
|
|
168
|
+
Persist.scoped(dir, id, "prompt", [legacy]),
|
|
169
|
+
createStore<{
|
|
170
|
+
prompt: Prompt
|
|
171
|
+
cursor?: number
|
|
172
|
+
context: {
|
|
173
|
+
items: (ContextItem & { key: string })[]
|
|
174
|
+
}
|
|
175
|
+
}>({
|
|
176
|
+
prompt: clonePrompt(DEFAULT_PROMPT),
|
|
177
|
+
cursor: undefined,
|
|
178
|
+
context: {
|
|
179
|
+
items: [],
|
|
180
|
+
},
|
|
181
|
+
}),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
const actions = createPromptActions(setStore)
|
|
185
|
+
|
|
186
|
+
return {
|
|
187
|
+
ready,
|
|
188
|
+
current: createMemo(() => store.prompt),
|
|
189
|
+
cursor: createMemo(() => store.cursor),
|
|
190
|
+
dirty: createMemo(() => !isPromptEqual(store.prompt, DEFAULT_PROMPT)),
|
|
191
|
+
context: {
|
|
192
|
+
items: createMemo(() => store.context.items),
|
|
193
|
+
add(item: ContextItem) {
|
|
194
|
+
const key = contextItemKey(item)
|
|
195
|
+
if (store.context.items.find((x) => x.key === key)) return
|
|
196
|
+
setStore("context", "items", (items) => [...items, { key, ...item }])
|
|
197
|
+
},
|
|
198
|
+
remove(key: string) {
|
|
199
|
+
setStore("context", "items", (items) => items.filter((x) => x.key !== key))
|
|
200
|
+
},
|
|
201
|
+
removeComment(path: string, commentID: string) {
|
|
202
|
+
setStore("context", "items", (items) =>
|
|
203
|
+
items.filter((item) => !(item.type === "file" && item.path === path && item.commentID === commentID)),
|
|
204
|
+
)
|
|
205
|
+
},
|
|
206
|
+
updateComment(path: string, commentID: string, next: Partial<FileContextItem> & { comment?: string }) {
|
|
207
|
+
setStore("context", "items", (items) =>
|
|
208
|
+
items.map((item) => {
|
|
209
|
+
if (item.type !== "file" || item.path !== path || item.commentID !== commentID) return item
|
|
210
|
+
const value = { ...item, ...next }
|
|
211
|
+
return { ...value, key: contextItemKey(value) }
|
|
212
|
+
}),
|
|
213
|
+
)
|
|
214
|
+
},
|
|
215
|
+
replaceComments(items: FileContextItem[]) {
|
|
216
|
+
setStore("context", "items", (current) => [
|
|
217
|
+
...current.filter((item) => !isCommentItem(item)),
|
|
218
|
+
...items.map((item) => ({ ...item, key: contextItemKey(item) })),
|
|
219
|
+
])
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
set: actions.set,
|
|
223
|
+
reset: actions.reset,
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export const { use: usePrompt, provider: PromptProvider } = createSimpleContext({
|
|
228
|
+
name: "Prompt",
|
|
229
|
+
gate: false,
|
|
230
|
+
init: () => {
|
|
231
|
+
const params = useParams()
|
|
232
|
+
const cache = new Map<string, PromptCacheEntry>()
|
|
233
|
+
|
|
234
|
+
const disposeAll = () => {
|
|
235
|
+
for (const entry of cache.values()) {
|
|
236
|
+
entry.dispose()
|
|
237
|
+
}
|
|
238
|
+
cache.clear()
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
onCleanup(disposeAll)
|
|
242
|
+
|
|
243
|
+
const prune = () => {
|
|
244
|
+
while (cache.size > MAX_PROMPT_SESSIONS) {
|
|
245
|
+
const first = cache.keys().next().value
|
|
246
|
+
if (!first) return
|
|
247
|
+
const entry = cache.get(first)
|
|
248
|
+
entry?.dispose()
|
|
249
|
+
cache.delete(first)
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const owner = getOwner()
|
|
254
|
+
const load = (dir: string, id: string | undefined) => {
|
|
255
|
+
const key = `${dir}:${id ?? WORKSPACE_KEY}`
|
|
256
|
+
const existing = cache.get(key)
|
|
257
|
+
if (existing) {
|
|
258
|
+
cache.delete(key)
|
|
259
|
+
cache.set(key, existing)
|
|
260
|
+
return existing.value
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const entry = createRoot(
|
|
264
|
+
(dispose) => ({
|
|
265
|
+
value: createPromptSession(dir, id),
|
|
266
|
+
dispose,
|
|
267
|
+
}),
|
|
268
|
+
owner,
|
|
269
|
+
)
|
|
270
|
+
|
|
271
|
+
cache.set(key, entry)
|
|
272
|
+
prune()
|
|
273
|
+
return entry.value
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const session = createMemo(() => load(params.dir!, params.id))
|
|
277
|
+
const pick = (scope?: Scope) => (scope ? load(scope.dir, scope.id) : session())
|
|
278
|
+
|
|
279
|
+
return {
|
|
280
|
+
ready: () => session().ready(),
|
|
281
|
+
current: () => session().current(),
|
|
282
|
+
cursor: () => session().cursor(),
|
|
283
|
+
dirty: () => session().dirty(),
|
|
284
|
+
context: {
|
|
285
|
+
items: () => session().context.items(),
|
|
286
|
+
add: (item: ContextItem) => session().context.add(item),
|
|
287
|
+
remove: (key: string) => session().context.remove(key),
|
|
288
|
+
removeComment: (path: string, commentID: string) => session().context.removeComment(path, commentID),
|
|
289
|
+
updateComment: (path: string, commentID: string, next: Partial<FileContextItem> & { comment?: string }) =>
|
|
290
|
+
session().context.updateComment(path, commentID, next),
|
|
291
|
+
replaceComments: (items: FileContextItem[]) => session().context.replaceComments(items),
|
|
292
|
+
},
|
|
293
|
+
set: (prompt: Prompt, cursorPosition?: number, scope?: Scope) => pick(scope).set(prompt, cursorPosition),
|
|
294
|
+
reset: (scope?: Scope) => pick(scope).reset(),
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
})
|