@soederpop/luca 0.0.26 → 0.0.29
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/docs/examples/assistant-with-process-manager.md +84 -0
- package/docs/examples/structured-output-with-assistants.md +144 -0
- package/docs/examples/websocket-ask-and-reply-example.md +128 -0
- package/docs/window-manager-fix.md +249 -0
- package/package.json +1 -1
- package/src/agi/features/assistant.ts +132 -3
- package/src/agi/features/conversation.ts +135 -45
- package/src/agi/lib/interceptor-chain.ts +79 -0
- package/src/bootstrap/generated.ts +360 -1
- package/src/cli/build-info.ts +2 -2
- package/src/clients/websocket.ts +76 -1
- package/src/helper.ts +29 -2
- package/src/introspection/generated.agi.ts +1379 -663
- package/src/introspection/generated.node.ts +1126 -542
- package/src/introspection/generated.web.ts +1 -1
- package/src/node/features/file-manager.ts +22 -1
- package/src/node/features/fs.ts +41 -3
- package/src/node/features/helpers.ts +25 -18
- package/src/node/features/ipc-socket.ts +370 -180
- package/src/node/features/process-manager.ts +316 -49
- package/src/node/features/window-manager.ts +843 -235
- package/src/scaffolds/generated.ts +1 -1
- package/src/servers/socket.ts +87 -0
- package/src/web/clients/socket.ts +22 -6
- package/test/interceptor-chain.test.ts +61 -0
- package/test/websocket-ask.test.ts +101 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { setBuildTimeData, setContainerBuildTimeData } from './index.js';
|
|
2
2
|
|
|
3
3
|
// Auto-generated introspection registry data
|
|
4
|
-
// Generated at: 2026-03-
|
|
4
|
+
// Generated at: 2026-03-24T01:41:39.159Z
|
|
5
5
|
|
|
6
6
|
setBuildTimeData('features.containerLink', {
|
|
7
7
|
"id": "features.containerLink",
|
|
@@ -3,7 +3,7 @@ import { FeatureEventsSchema, FeatureStateSchema, FeatureOptionsSchema } from '.
|
|
|
3
3
|
import { State } from "../../state.js";
|
|
4
4
|
import { Feature } from "../feature.js";
|
|
5
5
|
import { parse, relative, join as pathJoin } from "path";
|
|
6
|
-
import { statSync, readFileSync, existsSync } from "fs";
|
|
6
|
+
import { statSync, readFileSync, existsSync, readdirSync, lstatSync } from "fs";
|
|
7
7
|
import micromatch from "micromatch";
|
|
8
8
|
import { castArray } from "lodash-es";
|
|
9
9
|
import chokidar from "chokidar";
|
|
@@ -339,7 +339,28 @@ export class FileManager<
|
|
|
339
339
|
}
|
|
340
340
|
}
|
|
341
341
|
}
|
|
342
|
+
|
|
343
|
+
// git ls-files doesn't traverse symlinked directories — walk them via fs
|
|
344
|
+
// to pick up their contents. fs.walk now follows symlinks natively.
|
|
345
|
+
try {
|
|
346
|
+
const topEntries = readdirSync(cwd, { withFileTypes: true });
|
|
347
|
+
for (const entry of topEntries) {
|
|
348
|
+
if (entry.isSymbolicLink()) {
|
|
349
|
+
const fullPath = pathJoin(cwd, entry.name);
|
|
350
|
+
try {
|
|
351
|
+
const target = statSync(fullPath);
|
|
352
|
+
if (target.isDirectory()) {
|
|
353
|
+
const walked = await fs.walkAsync(fullPath, { exclude });
|
|
354
|
+
for (const absFile of walked.files) {
|
|
355
|
+
fileIds.push(relative(cwd, absFile));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
} catch {}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
} catch {}
|
|
342
362
|
} else {
|
|
363
|
+
// fs.walkAsync follows symlinks, so non-git repos get symlink support for free
|
|
343
364
|
await fs.walkAsync(cwd).then(({ files } : { files: string[] }) => fileIds.push(...files));
|
|
344
365
|
}
|
|
345
366
|
|
package/src/node/features/fs.ts
CHANGED
|
@@ -9,11 +9,13 @@ import {
|
|
|
9
9
|
readFileSync,
|
|
10
10
|
cpSync,
|
|
11
11
|
renameSync,
|
|
12
|
+
lstatSync,
|
|
13
|
+
realpathSync,
|
|
12
14
|
|
|
13
15
|
rmSync as nodeRmSync,
|
|
14
16
|
} from "fs";
|
|
15
17
|
import { join, resolve, dirname, relative } from "path";
|
|
16
|
-
import { readFile, stat, unlink, mkdir, writeFile, appendFile, readdir, cp, rename, rm as nodeRm } from "fs/promises";
|
|
18
|
+
import { readFile, stat, lstat, realpath, unlink, mkdir, writeFile, appendFile, readdir, cp, rename, rm as nodeRm } from "fs/promises";
|
|
17
19
|
import { native as rimraf } from 'rimraf'
|
|
18
20
|
|
|
19
21
|
type WalkOptions = {
|
|
@@ -40,6 +42,16 @@ function matchesPattern(filePath: string, patterns: string[]): boolean {
|
|
|
40
42
|
})
|
|
41
43
|
}
|
|
42
44
|
|
|
45
|
+
/** Sync: check if a symlink target is a directory */
|
|
46
|
+
function lstatFollowIsDir(fullPath: string): boolean {
|
|
47
|
+
try { return statSync(fullPath).isDirectory() } catch { return false }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Async: check if a symlink target is a directory */
|
|
51
|
+
async function lstatFollowIsDirAsync(fullPath: string): Promise<boolean> {
|
|
52
|
+
try { return (await stat(fullPath)).isDirectory() } catch { return false }
|
|
53
|
+
}
|
|
54
|
+
|
|
43
55
|
/**
|
|
44
56
|
* The FS feature provides methods for interacting with the file system, relative to the
|
|
45
57
|
* container's cwd.
|
|
@@ -472,6 +484,28 @@ export class FS extends Feature {
|
|
|
472
484
|
return stat(filePath).then(() => true).catch(() => false)
|
|
473
485
|
}
|
|
474
486
|
|
|
487
|
+
/**
|
|
488
|
+
* Checks if a path is a symbolic link.
|
|
489
|
+
*
|
|
490
|
+
* @param {string} path - The path to check
|
|
491
|
+
* @returns {boolean} True if the path is a symlink
|
|
492
|
+
*/
|
|
493
|
+
isSymlink(path: string): boolean {
|
|
494
|
+
const filePath = this.container.paths.resolve(path);
|
|
495
|
+
try { return lstatSync(filePath).isSymbolicLink() } catch { return false }
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Resolves a symlink to its real path. Returns the resolved path as-is if not a symlink.
|
|
500
|
+
*
|
|
501
|
+
* @param {string} path - The path to resolve
|
|
502
|
+
* @returns {string} The real path after resolving all symlinks
|
|
503
|
+
*/
|
|
504
|
+
realpath(path: string): string {
|
|
505
|
+
const filePath = this.container.paths.resolve(path);
|
|
506
|
+
return realpathSync(filePath)
|
|
507
|
+
}
|
|
508
|
+
|
|
475
509
|
/**
|
|
476
510
|
* Synchronously returns the stat object for a file or directory.
|
|
477
511
|
*
|
|
@@ -811,7 +845,9 @@ export class FS extends Feature {
|
|
|
811
845
|
const fullPath = join(baseDir, name);
|
|
812
846
|
const relativePath = relative(resolvedBase, fullPath)
|
|
813
847
|
const outputPath = useRelative ? relativePath : fullPath;
|
|
814
|
-
|
|
848
|
+
// Follow symlinks: isDirectory() returns false for symlinks,
|
|
849
|
+
// so check isSymbolicLink() and resolve to the real target
|
|
850
|
+
const isDir = entry.isDirectory() || (entry.isSymbolicLink() && lstatFollowIsDir(fullPath));
|
|
815
851
|
|
|
816
852
|
if (excludePatterns.length && matchesPattern(relativePath, excludePatterns)) {
|
|
817
853
|
continue
|
|
@@ -888,7 +924,9 @@ export class FS extends Feature {
|
|
|
888
924
|
const fullPath = join(currentDir, name);
|
|
889
925
|
const relativePath = relative(resolvedBase, fullPath)
|
|
890
926
|
const outputPath = useRelative ? relativePath : fullPath;
|
|
891
|
-
|
|
927
|
+
// Follow symlinks: isDirectory() returns false for symlinks,
|
|
928
|
+
// so check isSymbolicLink() and resolve to the real target
|
|
929
|
+
const isDir = entry.isDirectory() || (entry.isSymbolicLink() && await lstatFollowIsDirAsync(fullPath));
|
|
892
930
|
|
|
893
931
|
if (excludePatterns.length && matchesPattern(relativePath, excludePatterns)) {
|
|
894
932
|
continue
|
|
@@ -438,24 +438,22 @@ export class Helpers extends Feature<HelpersState, HelpersOptions> {
|
|
|
438
438
|
}
|
|
439
439
|
} catch {}
|
|
440
440
|
|
|
441
|
-
// Try fileManager first (faster in git repos
|
|
441
|
+
// Try fileManager first (faster in git repos, and now symlink-aware),
|
|
442
|
+
// fall back to fs.walk which also follows symlinks natively.
|
|
442
443
|
let files: string[] = []
|
|
443
444
|
try {
|
|
444
445
|
const fm = await this.ensureFileManager()
|
|
445
|
-
// fileManager may store absolute or relative keys — use absolute patterns
|
|
446
446
|
const absPatterns = [`${dir}/*.ts`, `${dir}/**/*.ts`]
|
|
447
447
|
const relPatterns = [`${type}/*.ts`, `${type}/**/*.ts`]
|
|
448
448
|
const matched = fm.match([...absPatterns, ...relPatterns])
|
|
449
449
|
files = matched.map((f: string) => f.startsWith('/') ? f : resolve(this.rootDir, f))
|
|
450
450
|
} catch {}
|
|
451
451
|
|
|
452
|
-
// Fall back to
|
|
452
|
+
// Fall back to fs.walk if fileManager found nothing
|
|
453
453
|
if (files.length === 0) {
|
|
454
|
-
const {
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
files.push(resolve(dir, file))
|
|
458
|
-
}
|
|
454
|
+
const { fs } = this.container
|
|
455
|
+
const walked = fs.walk(dir, { include: ['**/*.ts'] })
|
|
456
|
+
files = walked.files
|
|
459
457
|
}
|
|
460
458
|
|
|
461
459
|
for (const absPath of files) {
|
|
@@ -550,10 +548,15 @@ export class Helpers extends Feature<HelpersState, HelpersOptions> {
|
|
|
550
548
|
*/
|
|
551
549
|
private async discoverCommandsViaVM(dir: string): Promise<void> {
|
|
552
550
|
this.seedVirtualModules()
|
|
553
|
-
const {
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
551
|
+
const { fs } = this.container
|
|
552
|
+
// Commands are top-level only (not recursive) — walk and filter to *.ts in the immediate dir
|
|
553
|
+
const walked = fs.walk(dir, { include: ['*.ts'] })
|
|
554
|
+
const tsFiles = walked.files
|
|
555
|
+
.map(f => parse(f))
|
|
556
|
+
.filter(p => p.dir === dir) // top-level only
|
|
557
|
+
.map(p => p.base)
|
|
558
|
+
|
|
559
|
+
for (const file of tsFiles) {
|
|
557
560
|
if (file === 'index.ts') continue
|
|
558
561
|
|
|
559
562
|
const absPath = resolve(dir, file)
|
|
@@ -619,10 +622,14 @@ export class Helpers extends Feature<HelpersState, HelpersOptions> {
|
|
|
619
622
|
*/
|
|
620
623
|
private async discoverSelectorsViaVM(dir: string): Promise<void> {
|
|
621
624
|
this.seedVirtualModules()
|
|
622
|
-
const {
|
|
623
|
-
const
|
|
625
|
+
const { fs } = this.container
|
|
626
|
+
const walked = fs.walk(dir, { include: ['*.ts'] })
|
|
627
|
+
const tsFiles = walked.files
|
|
628
|
+
.map(f => parse(f))
|
|
629
|
+
.filter(p => p.dir === dir)
|
|
630
|
+
.map(p => p.base)
|
|
624
631
|
|
|
625
|
-
for
|
|
632
|
+
for (const file of tsFiles) {
|
|
626
633
|
if (file === 'index.ts') continue
|
|
627
634
|
|
|
628
635
|
const absPath = resolve(dir, file)
|
|
@@ -661,10 +668,10 @@ export class Helpers extends Feature<HelpersState, HelpersOptions> {
|
|
|
661
668
|
* Actual mounting to an express server is handled separately by ExpressServer.useEndpoints().
|
|
662
669
|
*/
|
|
663
670
|
private async discoverEndpoints(dir: string): Promise<void> {
|
|
664
|
-
const {
|
|
665
|
-
const
|
|
671
|
+
const { fs } = this.container
|
|
672
|
+
const walked = fs.walk(dir, { include: ['**/*.ts'] })
|
|
666
673
|
|
|
667
|
-
for
|
|
674
|
+
for (const file of walked.files) {
|
|
668
675
|
try {
|
|
669
676
|
const mod = await this.loadModuleExports(file)
|
|
670
677
|
const endpointModule = mod.default || mod
|