agentplugged 1.0.1 → 1.0.3

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.
@@ -14500,6 +14500,10 @@ retrospective, an installation and a validation point never wait for it.
14500
14500
  scope: \`guard.creator_action\` \`scope_widen\`) or a decision of the architecture contradicted, and
14501
14501
  lets the write through in doubt. Reading, and writes in other tools, stay governed by the
14502
14502
  permission rules alone: \`read_secrets\` is always denied, whatever the session.
14503
+ 5. A write the engine refused is never redone by another path: not by a terminal command
14504
+ (\`sed -i\`, a redirection \`>\`, \`Set-Content\`, \`Out-File\`, a copy, a script), not by another tool.
14505
+ The agent stops, tells the coordinator which file it needs and why, and the coordinator offers
14506
+ the creator to widen the scope (\`guard.creator_action\` \`scope_widen\`) in one gesture.
14503
14507
  `;
14504
14508
  }
14505
14509
  /** The folder layout and the seven rules: in the short constitution and in the reference alike. */
@@ -17783,6 +17787,21 @@ function claudeActionRules(action, mainBranch) {
17783
17787
  push(rules, patternsOf(action, mainBranch).flatMap(claudeRules));
17784
17788
  return rules;
17785
17789
  }
17790
+ /**
17791
+ * The rules as Claude Code's settings hold them. A backslash right before the closing parenthesis
17792
+ * escapes it there, so such a rule is written with that one backslash doubled (the others are
17793
+ * read literally). Claude Code's `PowerShell` tool has its own rules: each `Bash(x)` rule gets
17794
+ * its `PowerShell(x)` twin, or a command would pass by being run through the other shell.
17795
+ */
17796
+ function claudeCommandRules(rules) {
17797
+ const out = [];
17798
+ for (const rule of rules) {
17799
+ const escaped = rule.endsWith("\\)") && !rule.endsWith("\\\\)") ? `${rule.slice(0, -1)}\\)` : rule;
17800
+ push(out, [escaped]);
17801
+ if (escaped.startsWith("Bash(")) push(out, [`PowerShell${escaped.slice(4)}`]);
17802
+ }
17803
+ return out;
17804
+ }
17786
17805
  /** The Claude Code rule that allows all the tools of the `agentplugged` MCP server. */
17787
17806
  const ENGINE_MCP_RULE = "mcp__agentplugged";
17788
17807
  /**
@@ -18991,13 +19010,22 @@ const WRITE_TOOLS = [
18991
19010
  text: "new_source"
18992
19011
  }
18993
19012
  ];
19013
+ /** The two tools that run a shell command in Claude Code: both carry it in `tool_input.command`. */
19014
+ const SHELL_MATCHER = "Bash|PowerShell";
19015
+ /**
19016
+ * The `if` conditions of a hook on the shell tools: each `Bash(x)` condition and its
19017
+ * `PowerShell(x)` twin, as a condition matches the calls of one tool only.
19018
+ */
19019
+ function shellConditions(conditions) {
19020
+ return conditions.flatMap((condition) => [condition, `PowerShell${condition.slice(4)}`]);
19021
+ }
18994
19022
  /** The hooks `sync` writes for Claude Code, from the rules in force. */
18995
19023
  function claudeTrustHooks(rules, mainBranch) {
18996
19024
  const session = hookValue("session_id");
18997
19025
  const pre = [];
18998
- const conditions = rules.install_dependency === "deny" ? [] : claudeActionRules("install_dependency", mainBranch).filter((entry) => entry.startsWith("Bash("));
19026
+ const conditions = rules.install_dependency === "deny" ? [] : shellConditions(claudeActionRules("install_dependency", mainBranch).filter((entry) => entry.startsWith("Bash(")));
18999
19027
  if (conditions.length > 0) pre.push({
19000
- matcher: "Bash",
19028
+ matcher: SHELL_MATCHER,
19001
19029
  hooks: conditions.map((condition) => handler("check_action", {
19002
19030
  action: "install_dependency",
19003
19031
  command: hookValue("tool_input.command"),
@@ -19032,9 +19060,9 @@ function claudeTrustHooks(rules, mainBranch) {
19032
19060
  })]
19033
19061
  },
19034
19062
  {
19035
- matcher: "Bash",
19036
- hooks: NETWORK_COMMANDS.map((condition) => handler("record_external_read", {
19037
- tool: "Bash",
19063
+ matcher: SHELL_MATCHER,
19064
+ hooks: shellConditions(NETWORK_COMMANDS).map((condition) => handler("record_external_read", {
19065
+ tool: condition.startsWith("PowerShell(") ? "PowerShell" : "Bash",
19038
19066
  source: hookValue("tool_input.command"),
19039
19067
  session
19040
19068
  }, condition))
@@ -19775,9 +19803,9 @@ const syncCommand = defineCommand({
19775
19803
  flavor: claudeFlavor(loaded.config.permissions?.mode),
19776
19804
  current: claudeSettings
19777
19805
  }, has("claude-code") ? {
19778
- deny: claudeWritable(unique([...denyRules(permissionInputs), ...claudeRules.deny])),
19779
- allow: claudeWritable(claudeRules.allow),
19780
- ask: claudeWritable(claudeRules.ask)
19806
+ deny: claudeCommandRules(unique([...denyRules(permissionInputs), ...claudeRules.deny])),
19807
+ allow: claudeCommandRules(claudeRules.allow),
19808
+ ask: claudeCommandRules(claudeRules.ask)
19781
19809
  } : {}, {
19782
19810
  deny: manifest.permissions,
19783
19811
  ...previousLists[CLAUDE_SETTINGS_PATH]
@@ -19809,7 +19837,7 @@ const syncCommand = defineCommand({
19809
19837
  count: owned.allow.length
19810
19838
  });
19811
19839
  }
19812
- const conflicts = [...has("claude-code") ? allowConflicts(CLAUDE_SETTINGS_PATH, claudeSettings?.value, claudeRules.allow, {
19840
+ const conflicts = [...has("claude-code") ? allowConflicts(CLAUDE_SETTINGS_PATH, claudeSettings?.value, claudeCommandRules(claudeRules.allow), {
19813
19841
  deny: manifest.permissions,
19814
19842
  ...previousLists[CLAUDE_SETTINGS_PATH]
19815
19843
  }) : [], ...has("cursor") ? allowConflicts(CURSOR_CLI_PATH, cursorSettings?.value, cursorRules.allow, { deny: previousCursor }) : []];
@@ -19878,14 +19906,6 @@ const CODEX_HOME_DENY = [
19878
19906
  function unique(rules) {
19879
19907
  return [...new Set(rules)];
19880
19908
  }
19881
- /**
19882
- * The rules Claude Code's settings can hold: a backslash before the closing parenthesis escapes it
19883
- * there, so such a rule would be malformed (the `C:/` spelling of the same root stays). The
19884
- * engine's own guards keep them: they read the rules themselves.
19885
- */
19886
- function claudeWritable(rules) {
19887
- return rules.filter((rule) => !rule.endsWith("\\)"));
19888
- }
19889
19909
  /** The tool a generated path belongs to, for the summary. */
19890
19910
  function groupOf(path) {
19891
19911
  if (path.startsWith(".claude/") || path === "CLAUDE.md") return "claude-code";
@@ -31012,7 +31032,7 @@ function readSource(tool, raw) {
31012
31032
  const toolName = truncate(cleanText(tool), 200) || "unknown";
31013
31033
  const given = cleanText(raw ?? "");
31014
31034
  const usable = given !== "" && !given.includes("${");
31015
- if (usable && toolName === "Bash") return {
31035
+ if (usable && (toolName === "Bash" || toolName === "PowerShell")) return {
31016
31036
  tool: toolName,
31017
31037
  source: truncate(hostOf$1(given, true) ?? commandName(given), 200)
31018
31038
  };
package/dist/main.js CHANGED
@@ -6447,6 +6447,415 @@ var NodeFolderReader = class {
6447
6447
  return new NodeWorkspaceFiles(path);
6448
6448
  }
6449
6449
  };
6450
+ /**
6451
+ * The helper's script: compiles the C#, says it is ready (or why it cannot be), then answers one
6452
+ * JSON line per request line until its input closes. Constrained language mode forbids `Add-Type`,
6453
+ * which is said before trying.
6454
+ */
6455
+ const HELPER_SCRIPT = `
6456
+ $ErrorActionPreference = 'Stop'
6457
+ $out = [Console]::Out
6458
+ try {
6459
+ $mode = [string]$ExecutionContext.SessionState.LanguageMode
6460
+ if ($mode -ne 'FullLanguage') { throw ('PowerShell is in ' + $mode + ' mode, which forbids compiling code') }
6461
+ Add-Type -ReferencedAssemblies 'System.Web.Extensions' -TypeDefinition @'
6462
+ ${String.raw`
6463
+ using System;
6464
+ using System.Runtime.InteropServices;
6465
+ using System.Text;
6466
+ using System.Collections.Generic;
6467
+ using System.Web.Script.Serialization;
6468
+
6469
+ public static class ApCredentials {
6470
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
6471
+ struct CREDENTIAL {
6472
+ public uint Flags;
6473
+ public uint Type;
6474
+ public string TargetName;
6475
+ public string Comment;
6476
+ public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
6477
+ public uint CredentialBlobSize;
6478
+ public IntPtr CredentialBlob;
6479
+ public uint Persist;
6480
+ public uint AttributeCount;
6481
+ public IntPtr Attributes;
6482
+ public string TargetAlias;
6483
+ public string UserName;
6484
+ }
6485
+
6486
+ [DllImport("advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)]
6487
+ static extern bool CredRead(string target, uint type, uint flags, out IntPtr credential);
6488
+ [DllImport("advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)]
6489
+ static extern bool CredWrite(ref CREDENTIAL credential, uint flags);
6490
+ [DllImport("advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode, SetLastError = true)]
6491
+ static extern bool CredDelete(string target, uint type, uint flags);
6492
+ [DllImport("advapi32.dll", EntryPoint = "CredEnumerateW", CharSet = CharSet.Unicode, SetLastError = true)]
6493
+ static extern bool CredEnumerate(string filter, uint flags, out uint count, out IntPtr credentials);
6494
+ [DllImport("advapi32.dll", EntryPoint = "CredFree")]
6495
+ static extern void CredFree(IntPtr buffer);
6496
+
6497
+ const uint Generic = 1;
6498
+ const uint PersistEnterprise = 3;
6499
+ const int NotFound = 1168;
6500
+
6501
+ static string Text(string b64) {
6502
+ return b64 == null ? null : Encoding.UTF8.GetString(Convert.FromBase64String(b64));
6503
+ }
6504
+ static string B64(string text) {
6505
+ return Convert.ToBase64String(Encoding.UTF8.GetBytes(text == null ? "" : text));
6506
+ }
6507
+ public static string Failure(int code, string message) {
6508
+ return "{\"ok\":false,\"code\":" + code + ",\"message\":\"" + B64(message) + "\"}";
6509
+ }
6510
+ static string Win32Failure(int code) {
6511
+ string message;
6512
+ try { message = new System.ComponentModel.Win32Exception(code).Message; } catch (Exception) { message = ""; }
6513
+ return Failure(code, message);
6514
+ }
6515
+
6516
+ // One request line in, one answer line out. The line is parsed here, not by a cmdlet, so that
6517
+ // PowerShell module logging (when a policy turns it on) never records a secret.
6518
+ public static string HandleLine(string line) {
6519
+ int id = 0;
6520
+ try {
6521
+ Dictionary<string, object> request =
6522
+ (Dictionary<string, object>)new JavaScriptSerializer().DeserializeObject(line);
6523
+ id = Convert.ToInt32(request["id"]);
6524
+ string answer = Handle(Field(request, "op"), Field(request, "target"), Field(request, "user"), Field(request, "secret"));
6525
+ return "{\"id\":" + id + "," + answer.Substring(1);
6526
+ } catch (Exception) {
6527
+ return "{\"id\":" + id + "," + Failure(0, "the credential helper could not read a request").Substring(1);
6528
+ }
6529
+ }
6530
+ static string Field(Dictionary<string, object> request, string name) {
6531
+ object value;
6532
+ return request.TryGetValue(name, out value) && value != null ? value.ToString() : "";
6533
+ }
6534
+
6535
+ public static string Handle(string op, string targetB64, string userB64, string secretB64) {
6536
+ try {
6537
+ string target = Text(targetB64);
6538
+ if (op == "read") return Read(target);
6539
+ if (op == "write") return Write(target, Text(userB64), Convert.FromBase64String(secretB64));
6540
+ if (op == "delete") return Delete(target);
6541
+ if (op == "list") return List(target);
6542
+ return Failure(0, "unknown operation");
6543
+ } catch (Exception) {
6544
+ return Failure(0, "the credential helper failed");
6545
+ }
6546
+ }
6547
+
6548
+ static string Read(string target) {
6549
+ IntPtr pointer;
6550
+ if (!CredRead(target, Generic, 0, out pointer)) {
6551
+ int error = Marshal.GetLastWin32Error();
6552
+ return error == NotFound ? "{\"ok\":true,\"found\":false}" : Win32Failure(error);
6553
+ }
6554
+ try {
6555
+ CREDENTIAL credential = (CREDENTIAL)Marshal.PtrToStructure(pointer, typeof(CREDENTIAL));
6556
+ byte[] blob = new byte[credential.CredentialBlobSize];
6557
+ if (blob.Length > 0) Marshal.Copy(credential.CredentialBlob, blob, 0, blob.Length);
6558
+ string answer = "{\"ok\":true,\"found\":true,\"secret\":\"" + Convert.ToBase64String(blob) + "\"}";
6559
+ Array.Clear(blob, 0, blob.Length);
6560
+ return answer;
6561
+ } finally {
6562
+ CredFree(pointer);
6563
+ }
6564
+ }
6565
+
6566
+ static string Write(string target, string user, byte[] blob) {
6567
+ IntPtr memory = Marshal.AllocHGlobal(blob.Length == 0 ? 1 : blob.Length);
6568
+ try {
6569
+ if (blob.Length > 0) Marshal.Copy(blob, 0, memory, blob.Length);
6570
+ CREDENTIAL credential = new CREDENTIAL();
6571
+ credential.Type = Generic;
6572
+ credential.TargetName = target;
6573
+ credential.CredentialBlobSize = (uint)blob.Length;
6574
+ credential.CredentialBlob = memory;
6575
+ credential.Persist = PersistEnterprise;
6576
+ credential.UserName = user;
6577
+ if (!CredWrite(ref credential, 0)) return Win32Failure(Marshal.GetLastWin32Error());
6578
+ return "{\"ok\":true}";
6579
+ } finally {
6580
+ byte[] zeros = new byte[blob.Length];
6581
+ if (zeros.Length > 0) Marshal.Copy(zeros, 0, memory, zeros.Length);
6582
+ Marshal.FreeHGlobal(memory);
6583
+ Array.Clear(blob, 0, blob.Length);
6584
+ }
6585
+ }
6586
+
6587
+ static string Delete(string target) {
6588
+ if (CredDelete(target, Generic, 0)) return "{\"ok\":true,\"found\":true}";
6589
+ int error = Marshal.GetLastWin32Error();
6590
+ return error == NotFound ? "{\"ok\":true,\"found\":false}" : Win32Failure(error);
6591
+ }
6592
+
6593
+ // The accounts of a service: every generic credential whose target is "<user>.<service>".
6594
+ static string List(string service) {
6595
+ uint count;
6596
+ IntPtr array;
6597
+ if (!CredEnumerate(null, 0, out count, out array)) {
6598
+ int error = Marshal.GetLastWin32Error();
6599
+ return error == NotFound ? "{\"ok\":true,\"users\":[]}" : Win32Failure(error);
6600
+ }
6601
+ try {
6602
+ List<string> users = new List<string>();
6603
+ for (int index = 0; index < count; index++) {
6604
+ IntPtr pointer = Marshal.ReadIntPtr(array, index * IntPtr.Size);
6605
+ CREDENTIAL credential = (CREDENTIAL)Marshal.PtrToStructure(pointer, typeof(CREDENTIAL));
6606
+ if (credential.Type == Generic && credential.UserName != null
6607
+ && credential.TargetName == credential.UserName + "." + service) {
6608
+ users.Add("\"" + B64(credential.UserName) + "\"");
6609
+ }
6610
+ }
6611
+ return "{\"ok\":true,\"users\":[" + string.Join(",", users.ToArray()) + "]}";
6612
+ } finally {
6613
+ CredFree(array);
6614
+ }
6615
+ }
6616
+ }
6617
+ `}
6618
+ '@
6619
+ $out.WriteLine('{"ready":true}')
6620
+ } catch {
6621
+ # Constrained language mode forbids method calls: the reason goes out as plain ASCII text,
6622
+ # through a cmdlet.
6623
+ $reason = ([string]$_) -replace '[^ -~]', ' ' -replace '["\\\\]', ' '
6624
+ Write-Output ('{"ready":false,"message":"' + $reason + '"}')
6625
+ exit 0
6626
+ }
6627
+ while ($null -ne ($line = [Console]::In.ReadLine())) {
6628
+ if ($line.Trim().Length -eq 0) { continue }
6629
+ $out.WriteLine([ApCredentials]::HandleLine($line))
6630
+ }
6631
+ `;
6632
+ /** Windows PowerShell 5.1 by its full path: never a `powershell` found on the PATH. */
6633
+ function windowsPowerShellPath() {
6634
+ const root = process.env.SystemRoot ?? process.env.windir;
6635
+ if (root === void 0) return void 0;
6636
+ const path = join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe");
6637
+ return existsSync(path) ? path : void 0;
6638
+ }
6639
+ /** The helper cannot be used on this machine (no PowerShell, constrained language, a crash...). */
6640
+ var PowerShellUnavailableError = class extends Error {
6641
+ name = "PowerShellUnavailableError";
6642
+ };
6643
+ const decode = (text) => Buffer.from(text ?? "", "base64").toString("utf8");
6644
+ /** One running helper process. */
6645
+ var Session = class Session {
6646
+ #child;
6647
+ #pending = /* @__PURE__ */ new Map();
6648
+ #nextId = 1;
6649
+ #buffer = "";
6650
+ #alive = true;
6651
+ #onReady;
6652
+ constructor(child) {
6653
+ this.#child = child;
6654
+ child.stdout?.setEncoding("utf8");
6655
+ child.stdout?.on("data", (chunk) => this.#receive(chunk));
6656
+ child.stdin?.on("error", () => {});
6657
+ child.on("error", () => this.#end("PowerShell could not be started"));
6658
+ child.on("close", () => this.#end("the PowerShell credential helper stopped"));
6659
+ }
6660
+ /** Starts a helper and waits until it is ready, or says why it cannot be. */
6661
+ static start(powershell, startupTimeoutMs) {
6662
+ const script = Buffer.from(HELPER_SCRIPT, "utf16le").toString("base64");
6663
+ const child = spawn(powershell, [
6664
+ "-NoLogo",
6665
+ "-NoProfile",
6666
+ "-NonInteractive",
6667
+ "-EncodedCommand",
6668
+ script
6669
+ ], {
6670
+ stdio: [
6671
+ "pipe",
6672
+ "pipe",
6673
+ "ignore"
6674
+ ],
6675
+ windowsHide: true
6676
+ });
6677
+ const session = new Session(child);
6678
+ return new Promise((resolve, reject) => {
6679
+ const timer = setTimeout(() => {
6680
+ session.close();
6681
+ reject(new PowerShellUnavailableError("PowerShell did not answer in time"));
6682
+ }, startupTimeoutMs);
6683
+ session.#onReady = (answer) => {
6684
+ clearTimeout(timer);
6685
+ if (answer.ready === true) {
6686
+ session.#idle();
6687
+ resolve(session);
6688
+ } else {
6689
+ session.close();
6690
+ reject(new PowerShellUnavailableError(answer.message || "PowerShell refused"));
6691
+ }
6692
+ };
6693
+ child.once("error", (error) => {
6694
+ clearTimeout(timer);
6695
+ reject(new PowerShellUnavailableError(error.message));
6696
+ });
6697
+ child.once("close", () => {
6698
+ clearTimeout(timer);
6699
+ reject(new PowerShellUnavailableError("PowerShell stopped before it was ready"));
6700
+ });
6701
+ });
6702
+ }
6703
+ get alive() {
6704
+ return this.#alive;
6705
+ }
6706
+ /** Stops the helper; its input closing also ends it if the kill fails. */
6707
+ close() {
6708
+ this.#alive = false;
6709
+ this.#child.stdin?.end();
6710
+ this.#child.kill();
6711
+ }
6712
+ /** Sends one request and waits for its answer (a failure of the Credential Manager is thrown). */
6713
+ request(fields, timeoutMs) {
6714
+ if (!this.#alive) return Promise.reject(/* @__PURE__ */ new Error("the PowerShell credential helper is not running"));
6715
+ const id = this.#nextId;
6716
+ this.#nextId += 1;
6717
+ return new Promise((resolve, reject) => {
6718
+ const timer = setTimeout(() => {
6719
+ this.#pending.delete(id);
6720
+ this.close();
6721
+ reject(/* @__PURE__ */ new Error("the PowerShell credential helper did not answer in time"));
6722
+ }, timeoutMs);
6723
+ this.#pending.set(id, {
6724
+ resolve,
6725
+ reject,
6726
+ timer
6727
+ });
6728
+ this.#busy();
6729
+ const line = JSON.stringify({
6730
+ id,
6731
+ op: fields.op,
6732
+ target: Buffer.from(fields.target, "utf8").toString("base64"),
6733
+ user: Buffer.from(fields.user ?? "", "utf8").toString("base64"),
6734
+ secret: fields.secret
6735
+ });
6736
+ this.#child.stdin?.write(`${line}\n`);
6737
+ });
6738
+ }
6739
+ #receive(chunk) {
6740
+ this.#buffer += chunk;
6741
+ for (let end = this.#buffer.indexOf("\n"); end >= 0; end = this.#buffer.indexOf("\n")) {
6742
+ const line = this.#buffer.slice(0, end).trim();
6743
+ this.#buffer = this.#buffer.slice(end + 1);
6744
+ if (line === "") continue;
6745
+ let answer;
6746
+ try {
6747
+ answer = JSON.parse(line);
6748
+ } catch {
6749
+ continue;
6750
+ }
6751
+ if (answer.ready !== void 0) this.#onReady?.(answer);
6752
+ else if (answer.id !== void 0) {
6753
+ const pending = this.#pending.get(answer.id);
6754
+ if (pending === void 0) continue;
6755
+ this.#pending.delete(answer.id);
6756
+ clearTimeout(pending.timer);
6757
+ pending.resolve(answer);
6758
+ if (this.#pending.size === 0) this.#idle();
6759
+ }
6760
+ }
6761
+ }
6762
+ #end(reason) {
6763
+ this.#alive = false;
6764
+ for (const pending of this.#pending.values()) {
6765
+ clearTimeout(pending.timer);
6766
+ pending.reject(new Error(reason));
6767
+ }
6768
+ this.#pending.clear();
6769
+ }
6770
+ /** While a request is in flight, Node must wait for the answer. */
6771
+ #busy() {
6772
+ for (const handle of this.#handles()) handle.ref?.();
6773
+ }
6774
+ /** With nothing in flight, the helper never keeps Node from exiting. */
6775
+ #idle() {
6776
+ for (const handle of this.#handles()) handle.unref?.();
6777
+ }
6778
+ #handles() {
6779
+ return [
6780
+ this.#child,
6781
+ this.#child.stdin,
6782
+ this.#child.stdout
6783
+ ];
6784
+ }
6785
+ };
6786
+ const STARTUP_TIMEOUT_MS = 3e4;
6787
+ const REQUEST_TIMEOUT_MS = 3e4;
6788
+ /** `CredentialBackend` through a PowerShell helper, started on the first request. */
6789
+ var PowerShellCredentials = class PowerShellCredentials {
6790
+ #powershell;
6791
+ #session;
6792
+ constructor(powershell) {
6793
+ this.#powershell = powershell;
6794
+ }
6795
+ /**
6796
+ * Starts the helper now. Rejects with `PowerShellUnavailableError` when PowerShell is absent, in
6797
+ * constrained language mode, or otherwise unable to compile the helper.
6798
+ */
6799
+ static async open(options = {}) {
6800
+ const powershell = options.powershell ?? windowsPowerShellPath();
6801
+ if (powershell === void 0) throw new PowerShellUnavailableError("Windows PowerShell was not found");
6802
+ const backend = new PowerShellCredentials(powershell);
6803
+ await backend.#ensure();
6804
+ return backend;
6805
+ }
6806
+ /** The running helper; a new one when the last stopped. */
6807
+ async #ensure() {
6808
+ for (let attempt = 0; attempt < 2; attempt += 1) {
6809
+ this.#session ??= Session.start(this.#powershell, STARTUP_TIMEOUT_MS).catch((error) => {
6810
+ this.#session = void 0;
6811
+ throw error;
6812
+ });
6813
+ const pending = this.#session;
6814
+ const session = await pending;
6815
+ if (session.alive) return session;
6816
+ if (this.#session === pending) this.#session = void 0;
6817
+ }
6818
+ throw new Error("the PowerShell credential helper keeps stopping");
6819
+ }
6820
+ async #ask(fields) {
6821
+ const answer = await (await this.#ensure()).request(fields, REQUEST_TIMEOUT_MS);
6822
+ if (answer.ok !== true) {
6823
+ const message = decode(answer.message) || "the Credential Manager refused";
6824
+ throw new Error(`Windows Credential Manager failed: ${message} (Windows error code ${answer.code ?? 0})`);
6825
+ }
6826
+ return answer;
6827
+ }
6828
+ async read(service, account) {
6829
+ const answer = await this.#ask({
6830
+ op: "read",
6831
+ target: `${account}.${service}`,
6832
+ user: account
6833
+ });
6834
+ if (answer.found !== true) return void 0;
6835
+ return Buffer.from(answer.secret ?? "", "base64").toString("utf16le");
6836
+ }
6837
+ async write(service, account, secret) {
6838
+ await this.#ask({
6839
+ op: "write",
6840
+ target: `${account}.${service}`,
6841
+ user: account,
6842
+ secret: Buffer.from(secret, "utf16le").toString("base64")
6843
+ });
6844
+ }
6845
+ async remove(service, account) {
6846
+ return (await this.#ask({
6847
+ op: "delete",
6848
+ target: `${account}.${service}`,
6849
+ user: account
6850
+ })).found === true;
6851
+ }
6852
+ async accounts(service) {
6853
+ return ((await this.#ask({
6854
+ op: "list",
6855
+ target: service
6856
+ })).users ?? []).map(decode);
6857
+ }
6858
+ };
6450
6859
  //#endregion
6451
6860
  //#region ../../packages/adapters-git/dist/node-key-store.js
6452
6861
  /**
@@ -6509,6 +6918,78 @@ function explainKeychainError(error, platform = process.platform) {
6509
6918
  if (platform !== "linux" || !/secret.?service|d-?bus|platform secure storage/i.test(message)) return error;
6510
6919
  return new Error(`the Secret Service of this Linux session could not be used: start one and unlock its default keyring (GNOME Keyring, KWallet or KeePassXC; on a machine without a desktop, gnome-keyring-daemon --unlock in a dbus-run-session), then run the command again. The system said: ${message}`, { cause: error });
6511
6920
  }
6921
+ /** What Windows says when it refuses a file under Smart App Control or an application control policy. */
6922
+ const BLOCKED_BY_POLICY = /application control|blocked this file|smart app control|signing level|code integrity|contr.le d.application|bloqu. ce fichier|error 4551|0x800711c7/i;
6923
+ /** The message of `error` and of the errors under its `cause`, outermost first. */
6924
+ function messageChain(error) {
6925
+ const messages = [];
6926
+ let current = error;
6927
+ for (let depth = 0; depth < 20 && current !== void 0 && current !== null; depth += 1) {
6928
+ messages.push(current instanceof Error ? current.message : String(current));
6929
+ current = current instanceof Error ? current.cause : void 0;
6930
+ }
6931
+ return messages;
6932
+ }
6933
+ /**
6934
+ * The error for a keychain that cannot be reached at all: `native` is why `@napi-rs/keyring` did
6935
+ * not load and, on Windows, `powershell` why the PowerShell helper could not be used. Says what
6936
+ * blocks, in plain words: the generic "npm has a bug" of the module's loader never stands alone,
6937
+ * and a Windows policy blocking the module is named, with the file.
6938
+ */
6939
+ function unavailableKeychainError(native, powershell) {
6940
+ const real = messageChain(native).filter((message) => !/cannot find native binding|npm has a bug/i.test(message));
6941
+ const blocked = real.find((message) => BLOCKED_BY_POLICY.test(message));
6942
+ const file = blocked === void 0 ? void 0 : /[A-Za-z]:\\[^\r\n"']*?\.node/.exec(blocked)?.[0];
6943
+ const viaPowerShell = powershell === void 0 ? "" : ` The keys could not be reached through Windows PowerShell either (${messageChain(powershell)[0]}).`;
6944
+ if (blocked !== void 0) {
6945
+ const said = blocked.split(/\r?\n/).find((line) => line.trim() !== "") ?? blocked;
6946
+ return new Error(`Windows blocked the system keychain module${file === void 0 ? "" : ` (${file})`}: Smart App Control or an application control policy refuses this unsigned file ("${said.trim()}").${viaPowerShell} Turn off Smart App Control (Windows Security, App & browser control) or allow this file in your application control policy, make sure Windows PowerShell can run in full language mode, then run the command again.`, { cause: native });
6947
+ }
6948
+ const why = real.length > 0 ? real.join("; ") : "Node gave no further reason";
6949
+ return new Error(`the system keychain module could not be loaded: ${why}.${viaPowerShell}`, { cause: native });
6950
+ }
6951
+ /** The keychain through `@napi-rs/keyring`. */
6952
+ async function nativeBackend() {
6953
+ const { AsyncEntry, findCredentialsAsync } = await load().catch((error) => {
6954
+ throw unavailableKeychainError(error);
6955
+ });
6956
+ const entry = (service, account) => new AsyncEntry(service, account, KEYCHAIN_ENTRY_OPTIONS);
6957
+ return {
6958
+ read: async (service, account) => await entry(service, account).getPassword() ?? void 0,
6959
+ write: (service, account, secret) => entry(service, account).setPassword(secret),
6960
+ remove: (service, account) => entry(service, account).deleteCredential(),
6961
+ accounts: async (service) => (await findCredentialsAsync(service)).map((found) => found.account)
6962
+ };
6963
+ }
6964
+ let system;
6965
+ /**
6966
+ * The system keychain of this machine. On Windows, the PowerShell helper first (no native module
6967
+ * to load, which Smart App Control may refuse), then `@napi-rs/keyring` when PowerShell cannot be
6968
+ * used; elsewhere `@napi-rs/keyring`. Chosen once per process, kept once it works.
6969
+ */
6970
+ function systemCredentials() {
6971
+ system ??= (async () => {
6972
+ let powershell;
6973
+ if (process.platform === "win32") try {
6974
+ return await PowerShellCredentials.open();
6975
+ } catch (error) {
6976
+ powershell = error;
6977
+ }
6978
+ try {
6979
+ return await nativeBackend();
6980
+ } catch (error) {
6981
+ throw powershell === void 0 ? error : unavailableKeychainError(nativeCause(error), powershell);
6982
+ }
6983
+ })().catch((error) => {
6984
+ system = void 0;
6985
+ throw error;
6986
+ });
6987
+ return system;
6988
+ }
6989
+ /** The error `@napi-rs/keyring` failed with, under the explained one `nativeBackend` throws. */
6990
+ function nativeCause(error) {
6991
+ return error instanceof Error && error.cause !== void 0 ? error.cause : error;
6992
+ }
6512
6993
  /** `operation`, its failure explained by `explainKeychainError`. */
6513
6994
  async function explained(operation) {
6514
6995
  try {
@@ -6519,8 +7000,9 @@ async function explained(operation) {
6519
7000
  }
6520
7001
  /**
6521
7002
  * `KeyStore` in the system keychain (Windows Credential Manager, macOS Keychain, the Secret
6522
- * Service on Linux), through `@napi-rs/keyring`. Tests pass their own service name
6523
- * and delete their entries afterwards: the machine's real entries are never touched.
7003
+ * Service on Linux): through a PowerShell helper on Windows (`systemCredentials`) and
7004
+ * `@napi-rs/keyring` elsewhere, or when PowerShell cannot be used. Tests pass their own service
7005
+ * name and delete their entries afterwards: the machine's real entries are never touched.
6524
7006
  */
6525
7007
  var NodeKeyStore = class {
6526
7008
  service;
@@ -6533,11 +7015,13 @@ var NodeKeyStore = class {
6533
7015
  * answering empty under concurrent access: elsewhere, none (no wait added to a first `init`).
6534
7016
  */
6535
7017
  settleDelays;
7018
+ #engine;
6536
7019
  constructor(options = {}) {
6537
7020
  this.service = options.service ?? "agentplugged";
6538
7021
  this.account = options.account ?? "machine";
6539
7022
  this.humanAccount = options.humanAccount ?? "human";
6540
7023
  this.presenceAccount = options.presenceAccount ?? "human-presence";
7024
+ this.#engine = options.engine ?? "system";
6541
7025
  this.settleDelays = options.settleDelays ?? (process.platform === "win32" ? KEYCHAIN_SETTLE_DELAYS_MS : []);
6542
7026
  }
6543
7027
  /** Waits `settleDelays[attempt]`; false once they are all spent. */
@@ -6547,21 +7031,17 @@ var NodeKeyStore = class {
6547
7031
  await setTimeout$1(delay);
6548
7032
  return true;
6549
7033
  }
6550
- /** The entry of `account`; on Linux, in the Secret Service only (`KEYCHAIN_ENTRY_OPTIONS`). */
6551
- async #entry(account) {
6552
- const { AsyncEntry } = await load();
6553
- return new AsyncEntry(this.service, account, KEYCHAIN_ENTRY_OPTIONS);
7034
+ #backend() {
7035
+ return this.#engine === "native" ? nativeBackend() : systemCredentials();
6554
7036
  }
6555
7037
  #read(account) {
6556
- return explained(async () => {
6557
- return await (await this.#entry(account)).getPassword() ?? void 0;
6558
- });
7038
+ return explained(async () => (await this.#backend()).read(this.service, account));
6559
7039
  }
6560
7040
  /** A full keychain gives `KEYCHAIN_FULL`; any other failure is thrown, explained. */
6561
7041
  async #write(account, value) {
6562
- const entry = await explained(() => this.#entry(account));
7042
+ const backend = await explained(() => this.#backend());
6563
7043
  try {
6564
- await entry.setPassword(value);
7044
+ await backend.write(this.service, account, value);
6565
7045
  } catch (error) {
6566
7046
  if (isKeychainFullError(error)) throw keychainFull(error instanceof Error ? error.message : String(error));
6567
7047
  throw explainKeychainError(error);
@@ -6596,7 +7076,7 @@ var NodeKeyStore = class {
6596
7076
  }
6597
7077
  /** Deletes the machine key's entry; true when there was one. */
6598
7078
  deleteMachineSecret() {
6599
- return explained(async () => (await this.#entry(this.account)).deleteCredential());
7079
+ return explained(async () => (await this.#backend()).remove(this.service, this.account));
6600
7080
  }
6601
7081
  /**
6602
7082
  * Deletes the entries of this service, product keys included (tests: a service of their own).
@@ -6606,13 +7086,14 @@ var NodeKeyStore = class {
6606
7086
  async deleteAll() {
6607
7087
  if (this.service === "agentplugged") throw new Error(`deleteAll refuses the real keychain service ${KEYCHAIN_SERVICE}`);
6608
7088
  let deleted = false;
6609
- const products = await load().then(({ findCredentialsAsync }) => findCredentialsAsync(this.service)).then((found) => found.map((entry) => entry.account).filter((name) => name.startsWith(PRODUCT_ACCOUNT))).catch(() => []);
7089
+ const backend = await explained(() => this.#backend());
7090
+ const products = await backend.accounts(this.service).then((found) => found.filter((name) => name.startsWith(PRODUCT_ACCOUNT))).catch(() => []);
6610
7091
  for (const account of [
6611
7092
  this.account,
6612
7093
  this.humanAccount,
6613
7094
  this.presenceAccount,
6614
7095
  ...products
6615
- ]) deleted = await (await this.#entry(account)).deleteCredential() || deleted;
7096
+ ]) deleted = await backend.remove(this.service, account) || deleted;
6616
7097
  return deleted;
6617
7098
  }
6618
7099
  };
@@ -59,6 +59,7 @@ back, and the decisions to take.
59
59
  other tool under his id (`ROLE_FORBIDDEN`): he never opens a checkpoint (an end of phase
60
60
  included), never starts or delivers a story, never submits a result in another agent's place.
61
61
  He never reports under another agent's id.
62
+ - When a developer reports a write the engine refused, Noah never lets it be redone by another path (a terminal command, a redirection, a copy): he proposes to the creator to widen the scope (`guard.creator_action` `scope_widen`), in one gesture.
62
63
  - Decisions belong to the creator: the scope, the options of a checkpoint, an approval, a new
63
64
  agent, and every point they kept for themselves. Noah recommends; the creator decides. The points
64
65
  they did not keep are decided by the agent responsible, never by Noah (`checkpoint.decide` is not
@@ -454,10 +454,12 @@ checks:
454
454
  - steps:
455
455
  en:
456
456
  - "On Linux, install and unlock a secret service (GNOME Keyring or KWallet), then run: npx agentplugged doctor"
457
- - "On Windows or macOS, the system keychain is part of the system: restart the session and try again."
457
+ - "On Windows, if the message says Smart App Control or an application control policy blocked a file: turn off Smart App Control (Windows Security, App & browser control) or allow that file in your policy, check that Windows PowerShell is not in constrained language mode, then run: npx agentplugged doctor"
458
+ - "On Windows or macOS otherwise, the system keychain is part of the system: restart the session and try again."
458
459
  fr:
459
460
  - "Sous Linux, installe et déverrouille un service de secrets (GNOME Keyring ou KWallet), puis lance : npx agentplugged doctor"
460
- - "Sous Windows ou macOS, le trousseau fait partie du système : redémarre la session et réessaie."
461
+ - "Sous Windows, si le message dit que Smart App Control ou une stratégie de contrôle des applications a bloqué un fichier : désactive Smart App Control (Sécurité Windows, Contrôle des applications et du navigateur) ou autorise ce fichier, vérifie que Windows PowerShell n'est pas en mode de langage restreint, puis lance : npx agentplugged doctor"
462
+ - "Sous Windows ou macOS sinon, le trousseau fait partie du système : redémarre la session et réessaie."
461
463
 
462
464
  - id: presence
463
465
  group: keys
@@ -1027,3 +1029,17 @@ signatures:
1027
1029
  en: "A write failed for lack of room"
1028
1030
  fr: "Une écriture a échoué faute de place"
1029
1031
  read: [disk.space]
1032
+ - id: keychain-blocked
1033
+ when:
1034
+ output:
1035
+ - "smart app control"
1036
+ - "application control policy"
1037
+ - "blocked the system keychain module"
1038
+ - "stratégie de contrôle d'application"
1039
+ say:
1040
+ en: "Windows refused to load the keychain's module (Smart App Control or an application control policy)"
1041
+ fr: "Windows a refusé de charger le module du trousseau (Smart App Control ou une stratégie de contrôle des applications)"
1042
+ read: [keychain]
1043
+ assumed:
1044
+ en: "The message names the blocked file; whether turning off Smart App Control or adding an allow rule is right for this machine is your call, I only checked that the keychain does not answer"
1045
+ fr: "Le message nomme le fichier bloqué ; désactiver Smart App Control ou ajouter une règle d'autorisation, c'est à toi de décider pour cette machine, je n'ai vérifié que le fait que le trousseau ne répond pas"
@@ -16803,7 +16803,8 @@ function alreadyRecorded(listing, read, session) {
16803
16803
  * source is a `${…}`), or for an absent source: when in doubt, the read counts.
16804
16804
  */
16805
16805
  function isLocalShellCommand(tool, source) {
16806
- if (tool.trim() !== "Bash" || source === void 0) return false;
16806
+ const shell = tool.trim();
16807
+ if (shell !== "Bash" && shell !== "PowerShell" || source === void 0) return false;
16807
16808
  const command = source.trim();
16808
16809
  if (command === "" || /^\$\{[^}]*\}$/.test(command) || /^https?:\/\//i.test(command)) return false;
16809
16810
  return !isNetworkCommand(command);
@@ -17101,9 +17102,14 @@ const FILE_EXTENSIONS = "ts|tsx|js|jsx|mjs|cjs|json|jsonc|md|mdx|yaml|yml|toml|p
17101
17102
  const CODE_SPAN = /`([^`\n]{1,200})`/g;
17102
17103
  const BARE_PATH = new RegExp(`(?<![\\w@./:~-])((?:\\./)?[\\w@.-]+(?:/[\\w@.*-]+)*\\.(?:${FILE_EXTENSIONS}))(?![\\w/-])`, "g");
17103
17104
  const BARE_FOLDER = /(?<![\w@./:~-])((?:\.\/)?[\w@.-]+(?:\/[\w@.*-]+)+\/)(?![\w])/g;
17105
+ const TEMPLATE_SEGMENT = /^(?:<[A-Za-z_][\w-]*>|\{[A-Za-z_][\w-]*\}|\[[A-Za-z_][\w-]*\]|:[A-Za-z_][\w-]*)$/;
17104
17106
  /** A token read as a path of the project, or undefined (a URL, an absolute path, a word). */
17105
17107
  function pathOf$1(token) {
17106
17108
  let text = token.trim().replaceAll("\\", "/").replace(/^\{project-root\}\//, "").replace(/^\.\//, "");
17109
+ if (text.includes("/")) {
17110
+ if (TEMPLATE_SEGMENT.test(text.split("/")[0] ?? "")) return void 0;
17111
+ text = text.split("/").map((part) => TEMPLATE_SEGMENT.test(part) ? "*" : part).join("/");
17112
+ }
17107
17113
  text = text.replace(/[:#][\w,-]*$/, "");
17108
17114
  if (text === "" || text.length > 200) return void 0;
17109
17115
  if (/^(?:[a-z][a-z0-9+.-]*:|\/|~|[A-Za-z]:)/.test(text) || text.includes("//")) return void 0;
@@ -18353,7 +18359,7 @@ async function judge(context, index, request, root) {
18353
18359
  kind: "refuse",
18354
18360
  rule: "scope",
18355
18361
  story: owner.id,
18356
- message: `AgentPlugged refused this write: rule scope, story ${owner.id} does not name ${oneLine(relative, 120)}. If it is needed, ask the creator to widen the scope (guard.creator_action scope_widen {story: "${owner.id}", paths: ["${oneLine(relative, 120)}"]}); otherwise leave that file alone. Nothing was written.`
18362
+ message: `AgentPlugged refused this write: rule scope, story ${owner.id} does not name ${oneLine(relative, 120)}. If it is needed, ask the creator to widen the scope (guard.creator_action scope_widen {story: "${owner.id}", paths: ["${oneLine(relative, 120)}"]}); otherwise leave that file alone: do not write it by another tool or command (sed -i, a redirection, Set-Content, a copy, a script), and tell the coordinator which file you need and why. Nothing was written.`
18357
18363
  };
18358
18364
  if (request.text !== void 0 && index.decisions !== void 0) {
18359
18365
  const broken = decisionViolation(index.decisions.guards, relative, request.text);
@@ -18363,7 +18369,7 @@ async function judge(context, index, request, root) {
18363
18369
  story: owner.id,
18364
18370
  decision: broken.decision,
18365
18371
  detail: broken.detail,
18366
- message: `AgentPlugged refused this write: decision ${broken.decision} of the architecture forbids it (${oneLine(broken.detail, 120)}). Follow the decision, or ask the creator to change it first. Nothing was written.`
18372
+ message: `AgentPlugged refused this write: decision ${broken.decision} of the architecture forbids it (${oneLine(broken.detail, 120)}). Follow the decision, or ask the creator to change it first; do not write the file by another tool or command. Nothing was written.`
18367
18373
  };
18368
18374
  }
18369
18375
  if (placed === void 0) return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentplugged",
3
- "version": "1.0.1",
3
+ "version": "1.0.3",
4
4
  "description": "Install the AgentPlugged method in a repository and make its agents callable from Claude Code, Codex, Antigravity/Gemini CLI and Cursor.",
5
5
  "releaseSummary": "The engine updates itself: your session moves to a newer version between two calls, with nothing to reconnect.",
6
6
  "license": "FSL-1.1-MIT",
@@ -47,14 +47,14 @@
47
47
  "koffi": "~3.3.2"
48
48
  },
49
49
  "devDependencies": {
50
- "@agentplugged/adapters-git": "1.0.1",
51
- "@agentplugged/adapters-tools": "1.0.1",
52
- "@agentplugged/core": "1.0.1",
53
- "@agentplugged/crypto": "1.0.1",
54
- "@agentplugged/i18n": "1.0.1",
55
- "@agentplugged/mcp": "1.0.1",
50
+ "@agentplugged/adapters-git": "1.0.3",
51
+ "@agentplugged/adapters-tools": "1.0.3",
52
+ "@agentplugged/core": "1.0.3",
53
+ "@agentplugged/crypto": "1.0.3",
54
+ "@agentplugged/i18n": "1.0.3",
55
+ "@agentplugged/mcp": "1.0.3",
56
56
  "@agentplugged/method": "0.0.0",
57
- "@agentplugged/protocol": "1.0.1",
57
+ "@agentplugged/protocol": "1.0.3",
58
58
  "@biomejs/biome": "~2.5.15",
59
59
  "@modelcontextprotocol/client": "~2.3.0",
60
60
  "@types/node": "^24.19.1",