loopat 0.1.46 → 0.1.47

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "loopat",
3
- "version": "0.1.46",
3
+ "version": "0.1.47",
4
4
  "description": "Self-hosted AI workspace built around context management — works solo, scales to teams",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://github.com/simpx/loopat",
@@ -52,6 +52,7 @@ import {
52
52
  personalKnowledgeDir,
53
53
  personalNotesDir,
54
54
  personalReposDir,
55
+ personalRepoCacheDir,
55
56
  LOOPAT_INSTALL_DIR,
56
57
  loopHomeUpper,
57
58
  workspaceHomeSkelDir,
@@ -128,6 +129,11 @@ export type ContainerOptions = {
128
129
  vaultName?: string
129
130
  knowledgeRw?: boolean
130
131
  mountAllLoops?: boolean
132
+ /** Source roster repo for this loop's workdir (meta.repo). The workdir is a
133
+ * `git worktree add` off this repo's bare mirror (repo-cache/<repo>), so its
134
+ * .git gitdir points into that mirror. When set, the mirror is bind-mounted
135
+ * at its host path (src=dst, rw) so the worktree resolves inside the sandbox. */
136
+ repo?: string
131
137
  /** Extra env vars to pre-bake into the container at create time. */
132
138
  extraEnv?: Record<string, string>
133
139
  /** Image to create the container from. Defaults to SANDBOX_IMAGE.
@@ -190,7 +196,7 @@ export type VolumeMount = {
190
196
  */
191
197
  export async function buildVolumeMounts(opts: ContainerOptions): Promise<VolumeMount[]> {
192
198
  const hostHome = homedir()
193
- const { loopId, createdBy, vaultName, knowledgeRw, mountAllLoops } = opts
199
+ const { loopId, createdBy, vaultName, knowledgeRw, mountAllLoops, repo } = opts
194
200
  const virtualHome = V_HOME(createdBy)
195
201
  const mounts: VolumeMount[] = []
196
202
 
@@ -204,6 +210,17 @@ export async function buildVolumeMounts(opts: ContainerOptions): Promise<VolumeM
204
210
 
205
211
  // Virtual mount points for AI / user:
206
212
  mounts.push({ src: loopWorkdir(loopId), dst: V_LOOP_WORKDIR(loopId) })
213
+
214
+ // Workdir built from a roster repo is a `git worktree add` off the repo's
215
+ // bare mirror (repo-cache/<repo>); the workdir's .git is a gitdir pointer INTO
216
+ // that mirror. Bind the mirror at its host path (src=dst) so the worktree's
217
+ // objects/refs/worktree-metadata resolve inside the sandbox — otherwise
218
+ // `git status` in the workdir is "fatal: not a git repository". rw because git
219
+ // writes the worktree's index/HEAD/logs under <mirror>/worktrees/<wt>/.
220
+ if (repo) {
221
+ const cache = personalRepoCacheDir(createdBy, repo)
222
+ mounts.push({ src: cache, dst: cache })
223
+ }
207
224
  mounts.push({ src: loopClaudeDir(loopId), dst: V_LOOP_CLAUDE(loopId) })
208
225
  mounts.push({
209
226
  src: loopContextKnowledge(loopId),
@@ -499,6 +499,7 @@ class LoopSession {
499
499
  vaultName: meta.config?.vault,
500
500
  knowledgeRw: meta.config?.knowledge_rw,
501
501
  mountAllLoops: meta.config?.mount_all_loops,
502
+ repo: meta.repo,
502
503
  extraEnv,
503
504
  ephemeralPorts: loopEphemeralPorts(meta),
504
505
  }, {
@@ -67,6 +67,7 @@ async function getOrSpawn(loopId: string, initCols = 80, initRows = 24): Promise
67
67
  vaultName: meta.config?.vault,
68
68
  knowledgeRw: meta.config?.knowledge_rw,
69
69
  mountAllLoops: meta.config?.mount_all_loops,
70
+ repo: meta.repo,
70
71
  extraEnv: personalCfg.vaultEnvs,
71
72
  ephemeralPorts: loopEphemeralPorts(meta),
72
73
  }, {