pi-landstrip 0.18.12 → 0.18.13
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 +0 -5
- package/dist/index.ts +77 -259
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -143,11 +143,6 @@ Pi Markdown agents are loaded from `~/.pi/agent/agents/` and `.pi/agents/`.
|
|
|
143
143
|
`landstrip.agent` settings override Markdown agents with the same name.
|
|
144
144
|
Project definitions override global definitions.
|
|
145
145
|
|
|
146
|
-
OpenCode agent imports are enabled by default. Set
|
|
147
|
-
`landstrip.opencode.showGlobalAgents` or `showLocalAgents` to `false` to disable
|
|
148
|
-
a source. OpenCode agents fill missing names; Pi agents take precedence.
|
|
149
|
-
Project imports require a trusted project.
|
|
150
|
-
|
|
151
146
|
See the main [Landstrip documentation](https://github.com/landstrip/landstrip#readme)
|
|
152
147
|
for `sandbox.json` fields and platform policy semantics.
|
|
153
148
|
|
package/dist/index.ts
CHANGED
|
@@ -18,8 +18,8 @@ import {
|
|
|
18
18
|
isIP,
|
|
19
19
|
Socket as NetSocket
|
|
20
20
|
} from "node:net";
|
|
21
|
-
import { homedir as
|
|
22
|
-
import { basename as
|
|
21
|
+
import { homedir as homedir3, tmpdir as tmpdir2 } from "node:os";
|
|
22
|
+
import { basename as basename2, dirname as dirname4, isAbsolute as isAbsolute3, join as join7, parse, resolve as resolve3 } from "node:path";
|
|
23
23
|
import { StringDecoder as StringDecoder2 } from "node:string_decoder";
|
|
24
24
|
import { fileURLToPath as fileURLToPath2, URL } from "node:url";
|
|
25
25
|
import {
|
|
@@ -80,8 +80,8 @@ import {
|
|
|
80
80
|
rmSync,
|
|
81
81
|
writeFileSync as writeFileSync3
|
|
82
82
|
} from "node:fs";
|
|
83
|
-
import { homedir as
|
|
84
|
-
import { basename
|
|
83
|
+
import { homedir as homedir2, tmpdir } from "node:os";
|
|
84
|
+
import { basename, dirname as dirname3, isAbsolute as isAbsolute2, join as join6, relative as relative3, resolve as resolve2, sep as sep3 } from "node:path";
|
|
85
85
|
import { fileURLToPath } from "node:url";
|
|
86
86
|
import {
|
|
87
87
|
getAgentDir as getAgentDir4,
|
|
@@ -363,38 +363,7 @@ class PermissionPromptCoordinator {
|
|
|
363
363
|
|
|
364
364
|
// config.ts
|
|
365
365
|
var MAX_SUBAGENTS = 16;
|
|
366
|
-
var
|
|
367
|
-
showGlobalAgents: true,
|
|
368
|
-
showLocalAgents: true
|
|
369
|
-
};
|
|
370
|
-
var LANDSTRIP_KEYS = new Set(["maxSubagents", "agent", "permission", "opencode"]);
|
|
371
|
-
var OPENCODE_KEYS = new Set(["showGlobalAgents", "showLocalAgents"]);
|
|
372
|
-
function readBooleanField(section, key, path) {
|
|
373
|
-
if (!(key in section))
|
|
374
|
-
return;
|
|
375
|
-
const value = section[key];
|
|
376
|
-
if (typeof value === "boolean")
|
|
377
|
-
return value;
|
|
378
|
-
throw new Error(`${path}.${key} must be a boolean`);
|
|
379
|
-
}
|
|
380
|
-
function normalizeOpenCode(value, path) {
|
|
381
|
-
if (value === undefined)
|
|
382
|
-
return {};
|
|
383
|
-
if (!isRecord2(value))
|
|
384
|
-
throw new Error(`${path} must be a JSON object`);
|
|
385
|
-
for (const key of Object.keys(value)) {
|
|
386
|
-
if (!OPENCODE_KEYS.has(key))
|
|
387
|
-
throw new Error(`${path}: unknown field ${key}`);
|
|
388
|
-
}
|
|
389
|
-
const result = {};
|
|
390
|
-
const showGlobalAgents = readBooleanField(value, "showGlobalAgents", path);
|
|
391
|
-
if (showGlobalAgents !== undefined)
|
|
392
|
-
result.showGlobalAgents = showGlobalAgents;
|
|
393
|
-
const showLocalAgents = readBooleanField(value, "showLocalAgents", path);
|
|
394
|
-
if (showLocalAgents !== undefined)
|
|
395
|
-
result.showLocalAgents = showLocalAgents;
|
|
396
|
-
return result;
|
|
397
|
-
}
|
|
366
|
+
var LANDSTRIP_KEYS = new Set(["maxSubagents", "agent", "permission"]);
|
|
398
367
|
function readJsonObject(path) {
|
|
399
368
|
let value;
|
|
400
369
|
try {
|
|
@@ -448,7 +417,6 @@ function readLandstripSettings(path) {
|
|
|
448
417
|
}
|
|
449
418
|
if ("permission" in settings.landstrip)
|
|
450
419
|
config.permission = settings.landstrip.permission;
|
|
451
|
-
config.opencode = normalizeOpenCode(settings.landstrip.opencode, `${path}: landstrip.opencode`);
|
|
452
420
|
expandAgentPromptReferences(config, path);
|
|
453
421
|
return config;
|
|
454
422
|
}
|
|
@@ -588,8 +556,7 @@ function loadLandstripConfig(cwd, includeProject = true, agentDir = getAgentDir(
|
|
|
588
556
|
}
|
|
589
557
|
if (!isRecord2(config.agent))
|
|
590
558
|
throw new Error("landstrip.agent must be an object");
|
|
591
|
-
|
|
592
|
-
return { ...config, maxSubagents, agent: config.agent, opencode, agentSources };
|
|
559
|
+
return { ...config, maxSubagents, agent: config.agent, agentSources };
|
|
593
560
|
}
|
|
594
561
|
function loadMaxSubagentsSettings(cwd, includeProject = true, agentDir = getAgentDir()) {
|
|
595
562
|
const global = loadLandstripConfig(cwd, false, agentDir).maxSubagents;
|
|
@@ -605,17 +572,8 @@ function loadMaxSubagentsSettings(cwd, includeProject = true, agentDir = getAgen
|
|
|
605
572
|
|
|
606
573
|
// opencode-agents.ts
|
|
607
574
|
import { existsSync as existsSync2, readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
|
|
608
|
-
import {
|
|
609
|
-
import { dirname as dirname2, extname, join as join3, relative, sep } from "node:path";
|
|
575
|
+
import { extname, join as join3, relative, sep } from "node:path";
|
|
610
576
|
import { parse as parseYaml } from "yaml";
|
|
611
|
-
import { parse as parseJsonc, printParseErrorCode } from "jsonc-parser";
|
|
612
|
-
var AGENT_DIR_PREFIXES = ["agent/", "agents/"];
|
|
613
|
-
function openCodeGlobalConfigDir() {
|
|
614
|
-
if (process.env.OPENCODE_CONFIG_DIR)
|
|
615
|
-
return process.env.OPENCODE_CONFIG_DIR;
|
|
616
|
-
const xdg = process.env.XDG_CONFIG_HOME;
|
|
617
|
-
return join3(xdg && xdg.length > 0 ? xdg : join3(homedir2(), ".config"), "opencode");
|
|
618
|
-
}
|
|
619
577
|
function listMarkdownFiles(root) {
|
|
620
578
|
if (!existsSync2(root))
|
|
621
579
|
return [];
|
|
@@ -646,13 +604,6 @@ function listMarkdownFiles(root) {
|
|
|
646
604
|
}
|
|
647
605
|
function agentNameFromPath(dir, filePath) {
|
|
648
606
|
const relativePath = relative(dir, filePath).split(sep).join("/");
|
|
649
|
-
for (const prefix of AGENT_DIR_PREFIXES) {
|
|
650
|
-
if (relativePath.startsWith(prefix)) {
|
|
651
|
-
const candidate = relativePath.slice(prefix.length);
|
|
652
|
-
const extension2 = extname(candidate);
|
|
653
|
-
return extension2.length > 0 ? candidate.slice(0, -extension2.length) : candidate;
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
607
|
const extension = extname(relativePath);
|
|
657
608
|
return extension.length > 0 ? relativePath.slice(0, -extension.length) : relativePath;
|
|
658
609
|
}
|
|
@@ -684,7 +635,7 @@ function convertToolsToPermission(tools) {
|
|
|
684
635
|
}
|
|
685
636
|
return permission;
|
|
686
637
|
}
|
|
687
|
-
function
|
|
638
|
+
function normalizeMarkdownAgentRaw(data, prompt) {
|
|
688
639
|
const known = new Set([
|
|
689
640
|
"name",
|
|
690
641
|
"model",
|
|
@@ -744,98 +695,27 @@ function normalizeOpenCodeAgentRaw(data, prompt) {
|
|
|
744
695
|
}
|
|
745
696
|
function parseMarkdownAgentRaw(content) {
|
|
746
697
|
const { data, body } = parseFrontmatter(content);
|
|
747
|
-
return
|
|
698
|
+
return normalizeMarkdownAgentRaw(data, body.trim());
|
|
748
699
|
}
|
|
749
|
-
function
|
|
750
|
-
const
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
if (errors.length > 0) {
|
|
755
|
-
const error = errors[0];
|
|
756
|
-
throw new Error(`${printParseErrorCode(error.error)} at offset ${error.offset}`);
|
|
757
|
-
}
|
|
758
|
-
if (!isRecord2(value))
|
|
759
|
-
throw new Error("config must contain a JSON object");
|
|
760
|
-
return value;
|
|
761
|
-
}
|
|
762
|
-
function loadAgentsFromConfigFile(path, source, agents, diagnostics) {
|
|
763
|
-
if (!existsSync2(path))
|
|
764
|
-
return;
|
|
765
|
-
try {
|
|
766
|
-
const config = parseOpenCodeConfig(path);
|
|
767
|
-
if (config.agent === undefined)
|
|
768
|
-
return;
|
|
769
|
-
if (!isRecord2(config.agent))
|
|
770
|
-
throw new Error("agent must be a JSON object");
|
|
771
|
-
for (const [name, value] of Object.entries(config.agent)) {
|
|
700
|
+
function loadPiMarkdownAgents(options) {
|
|
701
|
+
const agents = new Map;
|
|
702
|
+
const diagnostics = [];
|
|
703
|
+
for (const directory of options.directories) {
|
|
704
|
+
for (const filePath of listMarkdownFiles(directory.path)) {
|
|
772
705
|
try {
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
706
|
+
const content = readFileSync3(filePath, "utf8");
|
|
707
|
+
const raw = parseMarkdownAgentRaw(content);
|
|
708
|
+
const name = agentNameFromPath(directory.path, filePath);
|
|
709
|
+
if (!name) {
|
|
710
|
+
diagnostics.push(`${filePath}: agent name is empty`);
|
|
711
|
+
continue;
|
|
779
712
|
}
|
|
780
|
-
|
|
781
|
-
const raw = normalizeOpenCodeAgentRaw(value, prompt);
|
|
782
|
-
agents.set(name, { name, source, path, format: "json", raw });
|
|
713
|
+
agents.set(name, { name, source: directory.source, path: filePath, raw });
|
|
783
714
|
} catch (error) {
|
|
784
|
-
diagnostics.push(`${
|
|
785
|
-
}
|
|
786
|
-
}
|
|
787
|
-
} catch (error) {
|
|
788
|
-
diagnostics.push(`${path}: ${formatError(error)}`);
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
function loadConfigAgentsFromDir(dir, source, agents, diagnostics) {
|
|
792
|
-
for (const file of ["opencode.json", "opencode.jsonc"]) {
|
|
793
|
-
loadAgentsFromConfigFile(join3(dir, file), source, agents, diagnostics);
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
|
-
function loadAgentsFromRoot(root, nameRoot, source, agents, diagnostics) {
|
|
797
|
-
for (const filePath of listMarkdownFiles(root)) {
|
|
798
|
-
try {
|
|
799
|
-
const content = readFileSync3(filePath, "utf8");
|
|
800
|
-
const raw = parseMarkdownAgentRaw(content);
|
|
801
|
-
const name = agentNameFromPath(nameRoot, filePath);
|
|
802
|
-
if (!name) {
|
|
803
|
-
diagnostics.push(`${filePath}: agent name is empty`);
|
|
804
|
-
continue;
|
|
715
|
+
diagnostics.push(`${filePath}: ${formatError(error)}`);
|
|
805
716
|
}
|
|
806
|
-
agents.set(name, { name, source, path: filePath, format: "markdown", raw });
|
|
807
|
-
} catch (error) {
|
|
808
|
-
diagnostics.push(`${filePath}: ${formatError(error)}`);
|
|
809
717
|
}
|
|
810
718
|
}
|
|
811
|
-
}
|
|
812
|
-
function loadAgentsFromDir(dir, source, agents, diagnostics) {
|
|
813
|
-
for (const subdir of ["agent", "agents"]) {
|
|
814
|
-
loadAgentsFromRoot(join3(dir, subdir), dir, source, agents, diagnostics);
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
function loadPiMarkdownAgents(options) {
|
|
818
|
-
const agents = new Map;
|
|
819
|
-
const diagnostics = [];
|
|
820
|
-
for (const directory of options.directories) {
|
|
821
|
-
loadAgentsFromRoot(directory.path, directory.path, directory.source, agents, diagnostics);
|
|
822
|
-
}
|
|
823
|
-
return { agents, diagnostics };
|
|
824
|
-
}
|
|
825
|
-
function loadOpenCodeAgents(options) {
|
|
826
|
-
const agents = new Map;
|
|
827
|
-
const diagnostics = [];
|
|
828
|
-
if (options.includeGlobal) {
|
|
829
|
-
const globalDir = options.globalConfigDir ?? openCodeGlobalConfigDir();
|
|
830
|
-
loadConfigAgentsFromDir(globalDir, "global", agents, diagnostics);
|
|
831
|
-
loadAgentsFromDir(globalDir, "global", agents, diagnostics);
|
|
832
|
-
}
|
|
833
|
-
if (options.includeProject) {
|
|
834
|
-
loadConfigAgentsFromDir(options.cwd, "local", agents, diagnostics);
|
|
835
|
-
const projectDir = join3(options.cwd, ".opencode");
|
|
836
|
-
loadConfigAgentsFromDir(projectDir, "local", agents, diagnostics);
|
|
837
|
-
loadAgentsFromDir(projectDir, "local", agents, diagnostics);
|
|
838
|
-
}
|
|
839
719
|
return { agents, diagnostics };
|
|
840
720
|
}
|
|
841
721
|
|
|
@@ -950,38 +830,16 @@ function loadAgentCatalog(cwd, piAgentDir = getAgentDir2(), includeProject = tru
|
|
|
950
830
|
let configuredAgents = {};
|
|
951
831
|
let configuredPermission;
|
|
952
832
|
let agentSources = new Map;
|
|
953
|
-
let showGlobalAgents = false;
|
|
954
|
-
let showLocalAgents = false;
|
|
955
833
|
try {
|
|
956
834
|
const config = loadLandstripConfig(cwd, includeProject, piAgentDir);
|
|
957
835
|
maxSubagents = config.maxSubagents;
|
|
958
836
|
configuredAgents = config.agent;
|
|
959
837
|
configuredPermission = config.permission;
|
|
960
838
|
agentSources = new Map(config.agentSources);
|
|
961
|
-
showGlobalAgents = config.opencode.showGlobalAgents;
|
|
962
|
-
showLocalAgents = config.opencode.showLocalAgents;
|
|
963
839
|
} catch (error) {
|
|
964
840
|
diagnostics.push(formatError(error));
|
|
965
841
|
}
|
|
966
|
-
const openCode = loadOpenCodeAgents({
|
|
967
|
-
cwd,
|
|
968
|
-
includeGlobal: showGlobalAgents,
|
|
969
|
-
includeProject: showLocalAgents && includeProject
|
|
970
|
-
});
|
|
971
|
-
diagnostics.push(...openCode.diagnostics);
|
|
972
842
|
const normalized = new Map;
|
|
973
|
-
for (const imported of openCode.agents.values()) {
|
|
974
|
-
try {
|
|
975
|
-
const kind = imported.format === "markdown" ? "opencode-markdown" : "opencode-json";
|
|
976
|
-
normalized.set(imported.name, normalizeAgent(imported.name, imported.raw, imported.source, {
|
|
977
|
-
kind,
|
|
978
|
-
source: imported.source,
|
|
979
|
-
path: imported.path
|
|
980
|
-
}));
|
|
981
|
-
} catch (error) {
|
|
982
|
-
diagnostics.push(`${imported.path}: ${formatError(error)}`);
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
843
|
const piMarkdown = loadPiMarkdownAgents({
|
|
986
844
|
directories: [
|
|
987
845
|
{ path: join4(piAgentDir, "agents"), source: "global" },
|
|
@@ -1076,13 +934,13 @@ function agentSupportsMode(agent, mode) {
|
|
|
1076
934
|
|
|
1077
935
|
// agent-files.ts
|
|
1078
936
|
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
1079
|
-
import {
|
|
937
|
+
import { dirname as dirname2, join as join5, relative as relative2, sep as sep2 } from "node:path";
|
|
1080
938
|
import { getAgentDir as getAgentDir3, withFileMutationQueue as withFileMutationQueue2 } from "@earendil-works/pi-coding-agent";
|
|
1081
939
|
import {
|
|
1082
940
|
applyEdits,
|
|
1083
941
|
modify,
|
|
1084
|
-
parse as
|
|
1085
|
-
printParseErrorCode
|
|
942
|
+
parse as parseJsonc,
|
|
943
|
+
printParseErrorCode
|
|
1086
944
|
} from "jsonc-parser";
|
|
1087
945
|
var JSON_FORMAT = {
|
|
1088
946
|
formattingOptions: { insertSpaces: true, tabSize: 2, eol: `
|
|
@@ -1090,10 +948,10 @@ var JSON_FORMAT = {
|
|
|
1090
948
|
};
|
|
1091
949
|
function parseJsonDocument(content, label) {
|
|
1092
950
|
const errors = [];
|
|
1093
|
-
const value =
|
|
951
|
+
const value = parseJsonc(content, errors, { allowTrailingComma: true });
|
|
1094
952
|
if (errors.length > 0) {
|
|
1095
953
|
const error = errors[0];
|
|
1096
|
-
throw new Error(`${label}: ${
|
|
954
|
+
throw new Error(`${label}: ${printParseErrorCode(error.error)} at offset ${error.offset}`);
|
|
1097
955
|
}
|
|
1098
956
|
if (!isRecord2(value))
|
|
1099
957
|
throw new Error(`${label} must contain a JSON object`);
|
|
@@ -1108,38 +966,22 @@ function valueAtPath(root, path) {
|
|
|
1108
966
|
}
|
|
1109
967
|
return value;
|
|
1110
968
|
}
|
|
1111
|
-
function agentJsonPath(
|
|
1112
|
-
return
|
|
1113
|
-
}
|
|
1114
|
-
function agentContainerPath(kind) {
|
|
1115
|
-
return kind === "landstrip" ? ["landstrip", "agent"] : ["agent"];
|
|
969
|
+
function agentJsonPath(name) {
|
|
970
|
+
return ["landstrip", "agent", name];
|
|
1116
971
|
}
|
|
1117
|
-
function editorSnippet(
|
|
1118
|
-
|
|
1119
|
-
return `${JSON.stringify(value, null, 2)}
|
|
972
|
+
function editorSnippet(name, raw) {
|
|
973
|
+
return `${JSON.stringify({ landstrip: { agent: { [name]: raw } } }, null, 2)}
|
|
1120
974
|
`;
|
|
1121
975
|
}
|
|
1122
|
-
function parseEditorSnippet(content,
|
|
976
|
+
function parseEditorSnippet(content, name) {
|
|
1123
977
|
const root = parseJsonDocument(content, "Edited agent");
|
|
1124
|
-
const parent =
|
|
1125
|
-
const container = valueAtPath(root,
|
|
1126
|
-
|
|
1127
|
-
if (Object.keys(root).length !== 1 || root[rootKey] === undefined || !isRecord2(parent) || Object.keys(parent).length !== 1 || parent.agent === undefined || !isRecord2(container) || Object.keys(container).length !== 1 || !isRecord2(container[name])) {
|
|
978
|
+
const parent = valueAtPath(root, ["landstrip"]);
|
|
979
|
+
const container = valueAtPath(root, ["landstrip", "agent"]);
|
|
980
|
+
if (Object.keys(root).length !== 1 || root.landstrip === undefined || !isRecord2(parent) || Object.keys(parent).length !== 1 || parent.agent === undefined || !isRecord2(container) || Object.keys(container).length !== 1 || !isRecord2(container[name])) {
|
|
1128
981
|
throw new Error(`Edited JSON must contain only agent ${name}`);
|
|
1129
982
|
}
|
|
1130
983
|
return container[name];
|
|
1131
984
|
}
|
|
1132
|
-
function validateEditedAgent(kind, name, raw) {
|
|
1133
|
-
if (kind === "landstrip") {
|
|
1134
|
-
validateAgentRaw(name, raw);
|
|
1135
|
-
return;
|
|
1136
|
-
}
|
|
1137
|
-
const prompt = raw.prompt;
|
|
1138
|
-
if (prompt !== undefined && typeof prompt !== "string") {
|
|
1139
|
-
throw new Error(`agent ${name} prompt must be a string`);
|
|
1140
|
-
}
|
|
1141
|
-
validateAgentRaw(name, normalizeOpenCodeAgentRaw(raw, typeof prompt === "string" ? prompt : ""));
|
|
1142
|
-
}
|
|
1143
985
|
function ensureFinalNewline(content) {
|
|
1144
986
|
return content.endsWith(`
|
|
1145
987
|
`) ? content : `${content}
|
|
@@ -1151,7 +993,7 @@ async function writeJsonNode(path, nodePath, value) {
|
|
|
1151
993
|
`;
|
|
1152
994
|
parseJsonDocument(content, path);
|
|
1153
995
|
const edited = applyEdits(content, modify(content, nodePath, value, JSON_FORMAT));
|
|
1154
|
-
mkdirSync2(
|
|
996
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
1155
997
|
writeFileSync2(path, ensureFinalNewline(edited), { mode: 384 });
|
|
1156
998
|
});
|
|
1157
999
|
}
|
|
@@ -1177,11 +1019,11 @@ async function deleteJsonNode(path, nodePath, containerPaths) {
|
|
|
1177
1019
|
});
|
|
1178
1020
|
return deleted;
|
|
1179
1021
|
}
|
|
1180
|
-
function readAgentNode(path,
|
|
1022
|
+
function readAgentNode(path, name) {
|
|
1181
1023
|
if (!existsSync3(path))
|
|
1182
1024
|
return;
|
|
1183
1025
|
const root = parseJsonDocument(readFileSync4(path, "utf8"), path);
|
|
1184
|
-
const value = valueAtPath(root, agentJsonPath(
|
|
1026
|
+
const value = valueAtPath(root, agentJsonPath(name));
|
|
1185
1027
|
return isRecord2(value) ? { ...value } : undefined;
|
|
1186
1028
|
}
|
|
1187
1029
|
function permissionConfig(agent) {
|
|
@@ -1220,7 +1062,7 @@ function relocateGlobalPrompt(raw, agent) {
|
|
|
1220
1062
|
if (agent.origin?.source !== "global" || !agent.origin.path || typeof raw.prompt !== "string") {
|
|
1221
1063
|
return raw;
|
|
1222
1064
|
}
|
|
1223
|
-
return { ...raw, prompt: expandFileReferences(raw.prompt,
|
|
1065
|
+
return { ...raw, prompt: expandFileReferences(raw.prompt, dirname2(agent.origin.path)) };
|
|
1224
1066
|
}
|
|
1225
1067
|
function safeRelative(root, path) {
|
|
1226
1068
|
const value = relative2(root, path);
|
|
@@ -1234,19 +1076,13 @@ function projectMarkdownPath(cwd, agentDir, agent) {
|
|
|
1234
1076
|
const suffix = safeRelative(join5(agentDir, "agents"), origin.path) ?? `${agent.name}.md`;
|
|
1235
1077
|
return join5(cwd, ".pi", "agents", suffix);
|
|
1236
1078
|
}
|
|
1237
|
-
if (origin.kind === "opencode-markdown" && origin.path) {
|
|
1238
|
-
return join5(cwd, ".opencode", "agents", `${agent.name}.md`);
|
|
1239
|
-
}
|
|
1240
1079
|
throw new Error(`Agent ${agent.name} has no Markdown source`);
|
|
1241
1080
|
}
|
|
1242
|
-
function openCodeProjectPath(cwd, sourcePath) {
|
|
1243
|
-
return join5(cwd, ".opencode", basename(sourcePath));
|
|
1244
|
-
}
|
|
1245
1081
|
function projectSettingsPath(cwd, agentDir) {
|
|
1246
1082
|
return getPiConfigPaths(cwd, "settings.json", agentDir).projectPath;
|
|
1247
1083
|
}
|
|
1248
1084
|
function projectSettingsHasAgent(cwd, name, agentDir) {
|
|
1249
|
-
return readAgentNode(projectSettingsPath(cwd, agentDir),
|
|
1085
|
+
return readAgentNode(projectSettingsPath(cwd, agentDir), name) !== undefined;
|
|
1250
1086
|
}
|
|
1251
1087
|
function canDeleteProjectAgent(cwd, agent, agentDir = getAgentDir3()) {
|
|
1252
1088
|
try {
|
|
@@ -1256,21 +1092,16 @@ function canDeleteProjectAgent(cwd, agent, agentDir = getAgentDir3()) {
|
|
|
1256
1092
|
return false;
|
|
1257
1093
|
}
|
|
1258
1094
|
const origin = agent.origin;
|
|
1259
|
-
return Boolean(origin?.source === "local" && origin.path &&
|
|
1095
|
+
return Boolean(origin?.source === "local" && origin.path && origin.kind === "pi-markdown");
|
|
1260
1096
|
}
|
|
1261
1097
|
async function deleteProjectAgent(cwd, agent, agentDir = getAgentDir3()) {
|
|
1262
1098
|
const settingsPath = projectSettingsPath(cwd, agentDir);
|
|
1263
|
-
const deletedOverride = await deleteJsonNode(settingsPath, agentJsonPath(
|
|
1099
|
+
const deletedOverride = await deleteJsonNode(settingsPath, agentJsonPath(agent.name), [
|
|
1100
|
+
["landstrip", "agent"],
|
|
1101
|
+
["landstrip"]
|
|
1102
|
+
]);
|
|
1264
1103
|
const origin = agent.origin;
|
|
1265
|
-
if (origin?.source !== "local" || !origin.path)
|
|
1266
|
-
return deletedOverride;
|
|
1267
|
-
if (origin.kind === "opencode-json") {
|
|
1268
|
-
const deletedSource = await deleteJsonNode(origin.path, agentJsonPath("opencode", agent.name), [
|
|
1269
|
-
["agent"]
|
|
1270
|
-
]);
|
|
1271
|
-
return deletedOverride || deletedSource;
|
|
1272
|
-
}
|
|
1273
|
-
if (origin.kind !== "pi-markdown" && origin.kind !== "opencode-markdown") {
|
|
1104
|
+
if (origin?.source !== "local" || !origin.path || origin.kind !== "pi-markdown") {
|
|
1274
1105
|
return deletedOverride;
|
|
1275
1106
|
}
|
|
1276
1107
|
if (!existsSync3(origin.path))
|
|
@@ -1280,44 +1111,31 @@ async function deleteProjectAgent(cwd, agent, agentDir = getAgentDir3()) {
|
|
|
1280
1111
|
}
|
|
1281
1112
|
function prepareProjectAgentEditor(cwd, agent, agentDir = getAgentDir3()) {
|
|
1282
1113
|
const origin = agent.origin;
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
const useMarkdown = origin.kind === "pi-markdown" || origin.source === "local" || openCodeProjectEnabled;
|
|
1286
|
-
const target2 = useMarkdown ? projectMarkdownPath(cwd, agentDir, agent) : projectSettingsPath(cwd, agentDir);
|
|
1287
|
-
const projectOverride = useMarkdown ? undefined : readAgentNode(target2, "landstrip", agent.name);
|
|
1288
|
-
const disabledOverride = projectOverride && Object.keys(projectOverride).length === 1 && typeof projectOverride.disable === "boolean" ? projectOverride.disable : undefined;
|
|
1114
|
+
if (origin?.path && origin.kind === "pi-markdown") {
|
|
1115
|
+
const target2 = projectMarkdownPath(cwd, agentDir, agent);
|
|
1289
1116
|
return {
|
|
1290
1117
|
title: `Edit @${agent.name}`,
|
|
1291
1118
|
content: readFileSync4(origin.path, "utf8"),
|
|
1292
1119
|
async save(content) {
|
|
1293
|
-
|
|
1294
|
-
const raw2 = !useMarkdown && edited.disable === undefined && disabledOverride !== undefined ? { ...edited, disable: disabledOverride } : edited;
|
|
1295
|
-
validateAgentRaw(agent.name, raw2);
|
|
1296
|
-
if (!useMarkdown) {
|
|
1297
|
-
await writeJsonNode(target2, agentJsonPath("landstrip", agent.name), raw2);
|
|
1298
|
-
return;
|
|
1299
|
-
}
|
|
1120
|
+
validateAgentRaw(agent.name, parseMarkdownAgentRaw(content));
|
|
1300
1121
|
await withFileMutationQueue2(target2, async () => {
|
|
1301
|
-
mkdirSync2(
|
|
1122
|
+
mkdirSync2(dirname2(target2), { recursive: true });
|
|
1302
1123
|
writeFileSync2(target2, ensureFinalNewline(content), { mode: 384 });
|
|
1303
1124
|
});
|
|
1304
1125
|
}
|
|
1305
1126
|
};
|
|
1306
1127
|
}
|
|
1307
|
-
const useOpenCode = origin?.kind === "opencode-json" && origin.path !== undefined && (origin.source === "local" || openCodeProjectEnabled);
|
|
1308
|
-
const kind = useOpenCode ? "opencode" : "landstrip";
|
|
1309
1128
|
const sourcePath = origin?.path;
|
|
1310
|
-
const
|
|
1311
|
-
const
|
|
1312
|
-
const
|
|
1313
|
-
const target = kind === "opencode" && sourcePath ? origin?.source === "local" ? sourcePath : openCodeProjectPath(cwd, sourcePath) : projectSettingsPath(cwd, agentDir);
|
|
1129
|
+
const sourceRaw = origin?.kind === "settings" && sourcePath ? readAgentNode(sourcePath, agent.name) : undefined;
|
|
1130
|
+
const raw = relocateGlobalPrompt(sourceRaw ? sourceRaw : agent.raw ? { ...agent.raw } : normalizedAgentRaw(agent), agent);
|
|
1131
|
+
const target = projectSettingsPath(cwd, agentDir);
|
|
1314
1132
|
return {
|
|
1315
1133
|
title: `Edit @${agent.name}`,
|
|
1316
|
-
content: editorSnippet(
|
|
1134
|
+
content: editorSnippet(agent.name, raw),
|
|
1317
1135
|
async save(content) {
|
|
1318
|
-
const edited = parseEditorSnippet(content,
|
|
1319
|
-
|
|
1320
|
-
await writeJsonNode(target, agentJsonPath(
|
|
1136
|
+
const edited = parseEditorSnippet(content, agent.name);
|
|
1137
|
+
validateAgentRaw(agent.name, edited);
|
|
1138
|
+
await writeJsonNode(target, agentJsonPath(agent.name), edited);
|
|
1321
1139
|
}
|
|
1322
1140
|
};
|
|
1323
1141
|
}
|
|
@@ -1775,7 +1593,7 @@ var PRIMARY_AGENT_ENTRY = "landstrip.primary-agent";
|
|
|
1775
1593
|
var WORKER_ENV = "PI_LANDSTRIP_WORKER";
|
|
1776
1594
|
var CONTROL_TITLE = "pi-landstrip:control:v1";
|
|
1777
1595
|
var MAX_DEPTH = 3;
|
|
1778
|
-
var packageDir =
|
|
1596
|
+
var packageDir = dirname3(fileURLToPath(import.meta.url));
|
|
1779
1597
|
var MAX_TASK_OUTPUT_BYTES = 64 * 1024;
|
|
1780
1598
|
var INSPECTOR_BODY_LINES = 16;
|
|
1781
1599
|
var PI_THINKING_LEVELS = new Set(["off", "minimal", "low", "medium", "high", "xhigh", "max"]);
|
|
@@ -1821,7 +1639,7 @@ function resolvePiPackage() {
|
|
|
1821
1639
|
piPackageResolved = true;
|
|
1822
1640
|
try {
|
|
1823
1641
|
const entry = fileURLToPath(import.meta.resolve("@earendil-works/pi-coding-agent"));
|
|
1824
|
-
let dir2 =
|
|
1642
|
+
let dir2 = dirname3(entry);
|
|
1825
1643
|
for (;; ) {
|
|
1826
1644
|
const pkgPath = join6(dir2, "package.json");
|
|
1827
1645
|
if (existsSync4(pkgPath)) {
|
|
@@ -1829,7 +1647,7 @@ function resolvePiPackage() {
|
|
|
1829
1647
|
if (pkg)
|
|
1830
1648
|
return pkg;
|
|
1831
1649
|
}
|
|
1832
|
-
const parent =
|
|
1650
|
+
const parent = dirname3(dir2);
|
|
1833
1651
|
if (parent === dir2)
|
|
1834
1652
|
break;
|
|
1835
1653
|
dir2 = parent;
|
|
@@ -1843,7 +1661,7 @@ function resolvePiPackage() {
|
|
|
1843
1661
|
if (pkg)
|
|
1844
1662
|
return pkg;
|
|
1845
1663
|
}
|
|
1846
|
-
const parent =
|
|
1664
|
+
const parent = dirname3(dir);
|
|
1847
1665
|
if (parent === dir)
|
|
1848
1666
|
return;
|
|
1849
1667
|
dir = parent;
|
|
@@ -1861,7 +1679,7 @@ function readPiPackage(pkgPath) {
|
|
|
1861
1679
|
const binFile = typeof value.bin === "string" ? value.bin : isRecord2(value.bin) && typeof value.bin.pi === "string" ? value.bin.pi : undefined;
|
|
1862
1680
|
if (!binFile)
|
|
1863
1681
|
return;
|
|
1864
|
-
const cliEntry = join6(
|
|
1682
|
+
const cliEntry = join6(dirname3(pkgPath), binFile);
|
|
1865
1683
|
if (!existsSync4(cliEntry))
|
|
1866
1684
|
return;
|
|
1867
1685
|
cachedPiPackage = { cliEntry, version: parts.slice(0, 3) };
|
|
@@ -2298,15 +2116,15 @@ function canonicalPermissionPath(path, seen = new Set) {
|
|
|
2298
2116
|
if (seen.has(existing))
|
|
2299
2117
|
return path;
|
|
2300
2118
|
seen.add(existing);
|
|
2301
|
-
const target = resolve2(
|
|
2119
|
+
const target = resolve2(dirname3(existing), readlinkSync(existing), ...missing);
|
|
2302
2120
|
return canonicalPermissionPath(target, seen);
|
|
2303
2121
|
}
|
|
2304
2122
|
break;
|
|
2305
2123
|
} catch {
|
|
2306
|
-
const parent =
|
|
2124
|
+
const parent = dirname3(existing);
|
|
2307
2125
|
if (parent === existing)
|
|
2308
2126
|
return path;
|
|
2309
|
-
missing.unshift(
|
|
2127
|
+
missing.unshift(basename(existing));
|
|
2310
2128
|
existing = parent;
|
|
2311
2129
|
}
|
|
2312
2130
|
}
|
|
@@ -3603,11 +3421,11 @@ ${catalog.diagnostics.join(`
|
|
|
3603
3421
|
return;
|
|
3604
3422
|
});
|
|
3605
3423
|
try {
|
|
3606
|
-
const sessionWritePath = task.sessionDir ?? (task.sessionFile ?
|
|
3424
|
+
const sessionWritePath = task.sessionDir ?? (task.sessionFile ? dirname3(task.sessionFile) : undefined);
|
|
3607
3425
|
if (!sessionWritePath)
|
|
3608
3426
|
throw new Error("Subagent task has no session directory or file");
|
|
3609
3427
|
const cliEntry = invocation.args[0] ?? invocation.command;
|
|
3610
|
-
const cliRoot = dependencyRoot(cliEntry) ??
|
|
3428
|
+
const cliRoot = dependencyRoot(cliEntry) ?? dirname3(dirname3(cliEntry));
|
|
3611
3429
|
const extensionRoot = dependencyRoot(packageDir);
|
|
3612
3430
|
launch = await this.integration.prepareRpcWorker({
|
|
3613
3431
|
command: invocation.command,
|
|
@@ -3627,7 +3445,7 @@ ${catalog.diagnostics.join(`
|
|
|
3627
3445
|
...new Set([
|
|
3628
3446
|
ctx.cwd,
|
|
3629
3447
|
...agentBootstrapPaths(agentDir),
|
|
3630
|
-
join6(
|
|
3448
|
+
join6(homedir2(), ".agents", "skills"),
|
|
3631
3449
|
packageDir,
|
|
3632
3450
|
join6(packageDir, "node_modules"),
|
|
3633
3451
|
invocation.command,
|
|
@@ -3635,7 +3453,7 @@ ${catalog.diagnostics.join(`
|
|
|
3635
3453
|
extensionRoot,
|
|
3636
3454
|
...workerExtensions.flatMap((extension) => [
|
|
3637
3455
|
extension.entry,
|
|
3638
|
-
|
|
3456
|
+
dirname3(extension.entry),
|
|
3639
3457
|
dependencyRoot(extension.entry)
|
|
3640
3458
|
]),
|
|
3641
3459
|
task.sessionDir,
|
|
@@ -3849,7 +3667,7 @@ ${renderTaskResult(task.id, "error", message)}`);
|
|
|
3849
3667
|
storeTaskText(task, value, kind) {
|
|
3850
3668
|
if (Buffer.byteLength(value) <= MAX_TASK_OUTPUT_BYTES)
|
|
3851
3669
|
return value;
|
|
3852
|
-
const directory = task.sessionDir ?? (task.sessionFile ?
|
|
3670
|
+
const directory = task.sessionDir ?? (task.sessionFile ? dirname3(task.sessionFile) : undefined);
|
|
3853
3671
|
if (!directory)
|
|
3854
3672
|
throw new Error(`Task ${task.id} has no artifact directory`);
|
|
3855
3673
|
mkdirSync3(directory, { recursive: true });
|
|
@@ -4111,7 +3929,7 @@ var PROXY_ENVIRONMENT_VARIABLES = [
|
|
|
4111
3929
|
"https_proxy",
|
|
4112
3930
|
"all_proxy"
|
|
4113
3931
|
];
|
|
4114
|
-
var packageDir2 =
|
|
3932
|
+
var packageDir2 = dirname4(fileURLToPath2(import.meta.url));
|
|
4115
3933
|
var PERMISSION_OPTIONS = [
|
|
4116
3934
|
{ label: "Allow once", action: "once" },
|
|
4117
3935
|
{ label: "Allow for this session", action: "session" },
|
|
@@ -4139,7 +3957,7 @@ function loadSandboxConfig(cwd, includeProject) {
|
|
|
4139
3957
|
const globalConfigPath = join7(getAgentDir5(), "sandbox.json");
|
|
4140
3958
|
if (!existsSync5(globalConfigPath)) {
|
|
4141
3959
|
const templatePath = join7(packageDir2, "sandbox.json");
|
|
4142
|
-
mkdirSync4(
|
|
3960
|
+
mkdirSync4(dirname4(globalConfigPath), { recursive: true });
|
|
4143
3961
|
writeFileSync4(globalConfigPath, readFileSync6(templatePath, "utf-8"), "utf-8");
|
|
4144
3962
|
}
|
|
4145
3963
|
let globalConfig = JSON.parse(readFileSync6(join7(packageDir2, "sandbox.json"), "utf-8"));
|
|
@@ -4240,7 +4058,7 @@ function readOrEmptyConfig(configPath) {
|
|
|
4240
4058
|
}
|
|
4241
4059
|
}
|
|
4242
4060
|
function writeConfigFile(configPath, config) {
|
|
4243
|
-
mkdirSync4(
|
|
4061
|
+
mkdirSync4(dirname4(configPath), { recursive: true });
|
|
4244
4062
|
writeFileSync4(configPath, `${JSON.stringify(config, null, 2)}
|
|
4245
4063
|
`, "utf-8");
|
|
4246
4064
|
}
|
|
@@ -4336,10 +4154,10 @@ function canonicalizePath(filePath, cwd) {
|
|
|
4336
4154
|
const tail = [];
|
|
4337
4155
|
let probe = abs;
|
|
4338
4156
|
while (!existsSync5(probe)) {
|
|
4339
|
-
const parent =
|
|
4157
|
+
const parent = dirname4(probe);
|
|
4340
4158
|
if (parent === probe)
|
|
4341
4159
|
return abs;
|
|
4342
|
-
tail.unshift(
|
|
4160
|
+
tail.unshift(basename2(probe));
|
|
4343
4161
|
probe = parent;
|
|
4344
4162
|
}
|
|
4345
4163
|
try {
|
|
@@ -4377,9 +4195,9 @@ function pathUnderDirectory(filePath, dir) {
|
|
|
4377
4195
|
return normalizedFilePath.startsWith(normalizedDir + sep4);
|
|
4378
4196
|
}
|
|
4379
4197
|
function sessionScopeFor(filePath, baseDirectory) {
|
|
4380
|
-
const dir =
|
|
4198
|
+
const dir = dirname4(filePath);
|
|
4381
4199
|
const normalizedDir = normalizePathSeparators(dir);
|
|
4382
|
-
const home =
|
|
4200
|
+
const home = homedir3();
|
|
4383
4201
|
const boundaries = new Set;
|
|
4384
4202
|
if (home)
|
|
4385
4203
|
boundaries.add(home);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-landstrip",
|
|
3
|
-
"version": "0.18.
|
|
3
|
+
"version": "0.18.13",
|
|
4
4
|
"description": "Sandboxed Bash and process-backed agents for Pi",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"landstrip",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"ci:test": "npm run test"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@landstrip/landstrip": "^0.18.
|
|
42
|
+
"@landstrip/landstrip": "^0.18.13",
|
|
43
43
|
"jsonc-parser": "^3.3.1",
|
|
44
44
|
"minimatch": "^10.2.5",
|
|
45
45
|
"yaml": "^2.9.0"
|