@tokensmind/agent-network 0.1.0 → 0.1.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.
Files changed (62) hide show
  1. package/README.md +81 -7
  2. package/openclaw.plugin.json +2 -2
  3. package/package.json +18 -8
  4. package/skills/tokensmind-agent-network/SKILL.md +145 -0
  5. package/skills/tokensmind-agent-network/package.json +4 -0
  6. package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/action_support.py +3 -1
  7. package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/agent_network_runtime.py +61 -9
  8. package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/lib/action-executor.js +12 -1
  9. package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/lib/action-validation.js +5 -0
  10. package/skills/tokensmind-agent-network/scripts/lib/agent-actions.js +91 -0
  11. package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/lib/api-client.js +14 -2
  12. package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/lib/contact-action.js +1 -1
  13. package/skills/tokensmind-agent-network/scripts/lib/memory-actions.js +112 -0
  14. package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/lib/runtime/connector.js +8 -19
  15. package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/lib/runtime/credentialStore.js +60 -11
  16. package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/lib/runtime/requestPolicy.js +0 -11
  17. package/skills/tokensmind-agent-network/scripts/lib/runtime/systemCredentialStore.js +245 -0
  18. package/skills/tokensmind-agent-network/scripts/lib/runtime/systemCredentialSupport.js +71 -0
  19. package/skills/tokensmind-agent-network/scripts/memory_actions.py +131 -0
  20. package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/python_runtime/connector.py +8 -8
  21. package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/python_runtime/credential_store.py +56 -12
  22. package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/python_runtime/request_policy.py +1 -13
  23. package/skills/tokensmind-agent-network/scripts/python_runtime/system_credential_store.py +250 -0
  24. package/src/action-executor.js +12 -1
  25. package/src/action-validation.js +5 -0
  26. package/src/agent-actions.js +53 -6
  27. package/src/api-client.js +14 -2
  28. package/src/contact-action.js +1 -1
  29. package/src/index.js +4 -2
  30. package/src/memory-actions.js +112 -0
  31. package/src/runtime/connector.js +8 -19
  32. package/src/runtime/credentialStore.js +60 -11
  33. package/src/runtime/requestPolicy.js +0 -11
  34. package/src/runtime/systemCredentialStore.js +143 -74
  35. package/src/runtime/systemCredentialSupport.js +71 -0
  36. package/skills/tokensmind-agent-network-runtime/SKILL.md +0 -55
  37. package/skills/tokensmind-agent-network-runtime/scripts/lib/agent-actions.js +0 -44
  38. package/skills/tokensmind-agent-network-runtime/scripts/lib/runtime/systemCredentialStore.js +0 -176
  39. package/skills/tokensmind-agent-network-runtime/scripts/python_runtime/system_credential_store.py +0 -143
  40. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/action_executor.py +0 -0
  41. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/action_validation.py +0 -0
  42. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/agent-network-runtime.mjs +0 -0
  43. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/governance_actions.py +0 -0
  44. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/lib/action-context.js +0 -0
  45. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/lib/action-errors.js +0 -0
  46. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/lib/governance-actions.js +0 -0
  47. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/lib/messaging-actions.js +0 -0
  48. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/lib/runtime/browser.js +0 -0
  49. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/lib/runtime/constants.js +0 -0
  50. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/lib/runtime/crypto.js +0 -0
  51. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/lib/runtime/deviceAuthorization.js +0 -0
  52. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/lib/runtime/httpClient.js +0 -0
  53. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/lib/runtime/portableStore.js +0 -0
  54. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/lib/workflow-store.js +0 -0
  55. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/python_runtime/__init__.py +0 -0
  56. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/python_runtime/browser.py +0 -0
  57. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/python_runtime/constants.py +0 -0
  58. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/python_runtime/crypto.py +0 -0
  59. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/python_runtime/device_authorization.py +0 -0
  60. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/python_runtime/errors.py +0 -0
  61. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/python_runtime/http_client.py +0 -0
  62. /package/skills/{tokensmind-agent-network-runtime → tokensmind-agent-network}/scripts/python_runtime/portable_store.py +0 -0
@@ -0,0 +1,131 @@
1
+ import math
2
+ from urllib.parse import quote, urlencode
3
+
4
+ from action_support import ActionState
5
+ from action_validation import require_text, text
6
+
7
+ MEMORY_KINDS = frozenset(("agent_experience", "matching_preference"))
8
+ MEMORY_POLARITIES = frozenset(("negative", "positive"))
9
+ MEMORY_SCOPES = frozenset(("contextual", "global"))
10
+ # platform_event is written by the service alone and is rejected from this client.
11
+ MEMORY_SOURCES = frozenset(("model_inferred", "user_explicit"))
12
+ MEMORY_STATUSES = frozenset(("active", "deleted", "pending_review", "rejected"))
13
+ MAX_INFERRED_CONFIDENCE = 0.6
14
+ DEFAULT_LIST_LIMIT = 20
15
+ MAX_LIST_LIMIT = 100
16
+
17
+
18
+ def _choice(data, field, allowed):
19
+ value = text(data.get(field)).lower()
20
+ if value not in allowed:
21
+ raise ActionState("input_required", "%s has an unsupported value." % field, fields=[field])
22
+ return value
23
+
24
+
25
+ def _confidence(value, source_type):
26
+ if value in (None, ""):
27
+ return MAX_INFERRED_CONFIDENCE if source_type == "model_inferred" else 1
28
+ try:
29
+ confidence = float(value)
30
+ except (TypeError, ValueError) as error:
31
+ raise ActionState("input_required", "confidence must be a number.", fields=["confidence"]) from error
32
+ invalid = not math.isfinite(confidence) or confidence <= 0 or confidence > 1
33
+ inferred_high = source_type == "model_inferred" and confidence > MAX_INFERRED_CONFIDENCE
34
+ if invalid or inferred_high:
35
+ raise ActionState(
36
+ "input_required",
37
+ "confidence must be above 0 and no more than 0.6 for inferred memory.",
38
+ fields=["confidence"],
39
+ )
40
+ return confidence
41
+
42
+
43
+ def _owned_agent(api):
44
+ agents = api.request("GET", "/agent-network-api/agents?mine=1")
45
+ if len(agents) > 1:
46
+ raise ActionState("failed", "The account has more than one Agent profile.", code="AGENT_ACCOUNT_INVARIANT", retryable=False)
47
+ if not agents:
48
+ raise ActionState("failed", "Create the account Agent profile first.", code="AGENT_PROFILE_REQUIRED", retryable=False)
49
+ return agents[0]
50
+
51
+
52
+ def _agent_path(agent_id, suffix):
53
+ return "/agent-network-api/agents/%s/%s" % (quote(str(agent_id), safe=""), suffix)
54
+
55
+
56
+ def _optional_text(data, field):
57
+ return text(data.get(field)) or None
58
+
59
+
60
+ def _memory_body(data):
61
+ source_type = _choice(data, "sourceType", MEMORY_SOURCES)
62
+ scope = _choice(data, "scope", MEMORY_SCOPES) if data.get("scope") else "contextual"
63
+ context_text = "" if scope == "global" else require_text(data.get("contextText"), "contextText")
64
+ return {
65
+ "clientMemoryId": require_text(data.get("clientMemoryId"), "clientMemoryId"),
66
+ "confidence": _confidence(data.get("confidence"), source_type),
67
+ "contextText": context_text,
68
+ "conversationId": _optional_text(data, "conversationId"),
69
+ "expiresAt": _optional_text(data, "expiresAt"),
70
+ "kind": _choice(data, "kind", MEMORY_KINDS),
71
+ "polarity": _choice(data, "polarity", MEMORY_POLARITIES),
72
+ "requirementId": _optional_text(data, "requirementId"),
73
+ "scope": scope,
74
+ "sourceType": source_type,
75
+ "statement": require_text(data.get("statement"), "statement"),
76
+ "targetAgentId": _optional_text(data, "targetAgentId"),
77
+ }
78
+
79
+
80
+ def _integer(value, field, minimum, maximum=None):
81
+ if isinstance(value, bool):
82
+ raise ActionState("input_required", "%s must be an integer." % field, fields=[field])
83
+ try:
84
+ number = float(value)
85
+ except (TypeError, ValueError) as error:
86
+ raise ActionState("input_required", "%s must be an integer." % field, fields=[field]) from error
87
+ if not number.is_integer() or number < minimum or (maximum is not None and number > maximum):
88
+ raise ActionState("input_required", "%s must be an integer in range." % field, fields=[field])
89
+ return int(number)
90
+
91
+
92
+ def _list_query(data):
93
+ limit = _integer(data.get("limit", DEFAULT_LIST_LIMIT), "limit", 1, MAX_LIST_LIMIT)
94
+ params = {"limit": limit}
95
+ if data.get("offset") is not None:
96
+ params["offset"] = _integer(data.get("offset"), "offset", 0)
97
+ if data.get("status"):
98
+ params["status"] = _choice(data, "status", MEMORY_STATUSES)
99
+ return urlencode(params)
100
+
101
+
102
+ def get_memory_settings(api, _data, _key):
103
+ agent = _owned_agent(api)
104
+ return api.request("GET", _agent_path(agent["id"], "memory-settings"))
105
+
106
+
107
+ def propose_memory(api, data, key):
108
+ body = _memory_body(data)
109
+ agent = _owned_agent(api)
110
+ settings_path = _agent_path(agent["id"], "memory-settings")
111
+ settings = api.request("GET", settings_path)
112
+ if settings.get("collectionEnabled") is not True:
113
+ raise ActionState("failed", "Memory collection is disabled for this Agent.", code="MEMORY_COLLECTION_DISABLED", retryable=False)
114
+ return api.request(
115
+ "POST", _agent_path(agent["id"], "memories"), body,
116
+ key=key + ":memory:propose",
117
+ )
118
+
119
+
120
+ def list_memories(api, data, _key):
121
+ query = _list_query(data)
122
+ agent = _owned_agent(api)
123
+ path = _agent_path(agent["id"], "memories") + "?" + query
124
+ return api.request("GET", path)
125
+
126
+
127
+ def delete_memory(api, data, key):
128
+ memory_id = require_text(data.get("memoryId"), "memoryId")
129
+ agent = _owned_agent(api)
130
+ path = _agent_path(agent["id"], "memories") + "/" + quote(memory_id, safe="")
131
+ return api.request("DELETE", path, key=key + ":memory:delete")
@@ -12,11 +12,8 @@ from .errors import AgentNetworkHttpError
12
12
  from .http_client import UNSET
13
13
  from .request_policy import (
14
14
  build_business_url,
15
- is_public_discovery_request,
16
15
  validate_business_request,
17
16
  )
18
- from action_support import AuthorizationRequired
19
-
20
17
  INVALID_CREDENTIAL_CODES = frozenset((
21
18
  "AGENT_CREDENTIAL_EXPIRED",
22
19
  "AGENT_CREDENTIAL_INVALID",
@@ -38,6 +35,12 @@ def _is_http_error(error, codes):
38
35
  return isinstance(error, AgentNetworkHttpError) and error.code in codes
39
36
 
40
37
 
38
+ def _requires_authorization(error):
39
+ return isinstance(error, AgentNetworkHttpError) and (
40
+ error.status == 401 or error.code in INVALID_CREDENTIAL_CODES
41
+ )
42
+
43
+
41
44
  class AgentNetworkConnector:
42
45
  def __init__(self, store, browser, base_url, *, http, client):
43
46
  self._store = store
@@ -60,15 +63,13 @@ class AgentNetworkConnector:
60
63
  active = self._store.read("active")
61
64
  if pending:
62
65
  return self._resume_pending(pending, active, operation)
63
- if is_public_discovery_request(operation) and not reauthorize:
64
- return self._execute_business_request(operation)
65
66
  if not active or reauthorize:
66
67
  active = self._authorize(operation)
67
68
  return self._execute_and_finalize(self._store.read("pending"), active)
68
69
  try:
69
70
  return self._execute_business_request(operation, active["token"])
70
71
  except AgentNetworkHttpError as error:
71
- if error.code not in INVALID_CREDENTIAL_CODES:
72
+ if not _requires_authorization(error):
72
73
  raise
73
74
  self._store.remove("active")
74
75
  active = self._authorize(operation)
@@ -103,7 +104,6 @@ class AgentNetworkConnector:
103
104
  )
104
105
  self._store.write("pending", pending)
105
106
  self._browser.open(pending["authorization"]["verificationUrl"])
106
- raise AuthorizationRequired(pending["authorization"]["verificationUrl"])
107
107
  if pending["phase"] == "authorizing":
108
108
  return self._complete_authorization(pending)
109
109
  return self._store.read("active")
@@ -151,6 +151,6 @@ class AgentNetworkConnector:
151
151
  except AgentNetworkHttpError as error:
152
152
  if error.status < 500 and error.status != 429:
153
153
  self._store.remove("pending")
154
- if error.code in INVALID_CREDENTIAL_CODES:
154
+ if _requires_authorization(error):
155
155
  self._store.remove("active")
156
156
  raise
@@ -7,7 +7,10 @@ from .portable_store import (
7
7
  _origin_namespace,
8
8
  resolve_portable_state_dir,
9
9
  )
10
- from .system_credential_store import resolve_system_credential_store
10
+ from .system_credential_store import (
11
+ SystemCredentialStoreError,
12
+ resolve_system_credential_store,
13
+ )
11
14
 
12
15
  AUTO_SECURE_STORE = object()
13
16
 
@@ -94,11 +97,50 @@ def _file_stores(options):
94
97
  ]
95
98
 
96
99
 
100
+ def _validate_system_records(system_store):
101
+ for name in RECORD_NAMES:
102
+ system_store.read(name)
103
+
104
+
105
+ def _initialize_store(
106
+ system_store, fallback, fallback_dir, legacy_dirs, options, on_fallback):
107
+ record_dir = os.path.join(fallback_dir, _origin_namespace(options["origin"]))
108
+ legacy_sources = _file_stores({**options, "directories": legacy_dirs})
109
+ if system_store is None:
110
+ on_fallback({
111
+ "backend": None,
112
+ "directory": record_dir,
113
+ "reason": "No supported system credential store is available",
114
+ })
115
+ migrate_credential_stores(fallback, legacy_sources)
116
+ return fallback
117
+ try:
118
+ if not hasattr(system_store, "verify_availability"):
119
+ raise SystemCredentialStoreError(
120
+ "System credential store does not support availability verification",
121
+ )
122
+ system_store.verify_availability()
123
+ _validate_system_records(system_store)
124
+ sources = _file_stores({
125
+ **options, "directories": [fallback_dir] + legacy_dirs,
126
+ })
127
+ migrate_credential_stores(system_store, sources)
128
+ return system_store
129
+ except SystemCredentialStoreError as error:
130
+ on_fallback({
131
+ "backend": system_store.backend,
132
+ "directory": record_dir,
133
+ "reason": str(error),
134
+ })
135
+ migrate_credential_stores(fallback, legacy_sources)
136
+ return fallback
137
+
138
+
97
139
  def create_credential_store(
98
140
  origin, platform, *, state_dir=None, env=None, home_dir=None,
99
141
  secure_store=AUTO_SECURE_STORE,
100
142
  secure_store_resolver=resolve_system_credential_store,
101
- command_runner=None):
143
+ command_runner=None, on_fallback=None):
102
144
  environment = os.environ if env is None else env
103
145
  if state_dir:
104
146
  return PortableStore(
@@ -119,17 +161,19 @@ def create_credential_store(
119
161
  system_store = secure_store_resolver(**resolver_options)
120
162
  else:
121
163
  system_store = secure_store
122
- target = system_store or fallback
123
- source_dirs = []
124
- if system_store:
125
- source_dirs.append(fallback_dir)
126
- source_dirs.extend(resolve_legacy_state_dirs(platform, environment, home_dir))
127
- sources = _file_stores({
128
- "directories": list(dict.fromkeys(source_dirs)),
164
+ legacy_dirs = list(dict.fromkeys(
165
+ resolve_legacy_state_dirs(platform, environment, home_dir),
166
+ ))
167
+ return _initialize_store(
168
+ system_store,
169
+ fallback,
170
+ fallback_dir,
171
+ legacy_dirs,
172
+ {
129
173
  "origin": origin,
130
174
  "platform": platform,
131
175
  "env": environment,
132
176
  "home_dir": home_dir,
133
- })
134
- migrate_credential_stores(target, sources)
135
- return target
177
+ },
178
+ on_fallback or (lambda _event: None),
179
+ )
@@ -1,10 +1,7 @@
1
- from urllib.parse import parse_qsl, urljoin, urlsplit, urlunsplit
1
+ from urllib.parse import urljoin, urlsplit, urlunsplit
2
2
 
3
3
  from .constants import INTERNAL_PATH_PATTERNS, MUTATION_METHODS, SUPPORTED_METHODS
4
4
 
5
- PUBLIC_AGENT_DIRECTORY_PATH = "/agent-network-api/agents"
6
-
7
-
8
5
  def normalize_base_url(value):
9
6
  parsed = urlsplit(str(value))
10
7
  if parsed.scheme not in ("http", "https") or not parsed.hostname:
@@ -74,12 +71,3 @@ def build_business_url(base_url, path):
74
71
  if _is_internal_path(parsed.path):
75
72
  raise ValueError("This endpoint is not available through the ordinary-user connector")
76
73
  return url
77
-
78
-
79
- def is_public_discovery_request(operation):
80
- if operation.get("method") != "GET":
81
- return False
82
- parsed = urlsplit(operation.get("path", ""))
83
- query = parse_qsl(parsed.query)
84
- mine_requested = any(key == "mine" and value == "1" for key, value in query)
85
- return parsed.path == PUBLIC_AGENT_DIRECTORY_PATH and not mine_requested
@@ -0,0 +1,250 @@
1
+ import json
2
+ import os
3
+ import shutil
4
+ import subprocess
5
+ import uuid
6
+
7
+ from .portable_store import RECORD_NAMES, _validate_record
8
+
9
+ SERVICE_NAME = "TokensMind Agent Network"
10
+ APPLICATION_NAME = "tokensmind-agent-network"
11
+ MACOS_SECURITY_PATH = "/usr/bin/security"
12
+ MACOS_ITEM_NOT_FOUND = 44
13
+ SYSTEM_STORE_PROBE_LENGTH = 1024
14
+
15
+
16
+ class SystemCredentialStoreError(RuntimeError):
17
+ code = "SYSTEM_CREDENTIAL_STORE_UNAVAILABLE"
18
+
19
+
20
+ def _validate_record_name(name):
21
+ if name not in RECORD_NAMES:
22
+ raise ValueError("Unsupported portable state record: %s" % name)
23
+
24
+
25
+ def _parse_record(name, source):
26
+ try:
27
+ value = json.loads(source)
28
+ except json.JSONDecodeError as error:
29
+ raise SystemCredentialStoreError(
30
+ "System Agent Network %s credential is invalid JSON" % name,
31
+ ) from error
32
+ try:
33
+ _validate_record(name, value)
34
+ except ValueError as error:
35
+ raise SystemCredentialStoreError(
36
+ "System Agent Network %s credential has an invalid structure" % name,
37
+ ) from error
38
+ return value
39
+
40
+
41
+ def _command_failure(backend, operation, result):
42
+ return SystemCredentialStoreError(
43
+ "%s %s failed with exit code %s" % (backend, operation, result.returncode),
44
+ )
45
+
46
+
47
+ def run_credential_command(args, input_text=""):
48
+ return subprocess.run(
49
+ args,
50
+ input=input_text,
51
+ text=True,
52
+ stdout=subprocess.PIPE,
53
+ stderr=subprocess.PIPE,
54
+ check=False,
55
+ )
56
+
57
+
58
+ def _run(runner, args, input_text, backend, operation):
59
+ try:
60
+ return runner(args, input_text)
61
+ except OSError as error:
62
+ raise SystemCredentialStoreError(
63
+ "%s %s failed" % (backend, operation),
64
+ ) from error
65
+
66
+
67
+ def _verify_round_trip(write, read, remove, expected):
68
+ failure = None
69
+ try:
70
+ write(expected)
71
+ if read() != expected:
72
+ failure = SystemCredentialStoreError(
73
+ "System credential store round-trip failed",
74
+ )
75
+ except SystemCredentialStoreError as error:
76
+ failure = error
77
+ try:
78
+ remove()
79
+ except SystemCredentialStoreError as error:
80
+ if failure is None:
81
+ failure = error
82
+ if failure is not None:
83
+ raise failure
84
+
85
+
86
+ def _probe_value():
87
+ return str(uuid.uuid4()).ljust(SYSTEM_STORE_PROBE_LENGTH, "x")
88
+
89
+
90
+ class MacOSCredentialStore:
91
+ def __init__(self, namespace, runner=run_credential_command):
92
+ self._namespace = namespace
93
+ self._runner = runner
94
+
95
+ def _account(self, name):
96
+ _validate_record_name(name)
97
+ return "%s:%s" % (self._namespace, name)
98
+
99
+ def _read_source(self, account):
100
+ result = _run(self._runner, [
101
+ MACOS_SECURITY_PATH, "find-generic-password",
102
+ "-a", account, "-s", SERVICE_NAME, "-w",
103
+ ], "", "macOS Keychain", "read")
104
+ if result.returncode == MACOS_ITEM_NOT_FOUND:
105
+ return None
106
+ if result.returncode != 0:
107
+ raise _command_failure("macOS Keychain", "read", result)
108
+ return result.stdout.strip()
109
+
110
+ def _write_source(self, account, source):
111
+ result = _run(self._runner, [
112
+ MACOS_SECURITY_PATH, "add-generic-password",
113
+ "-a", account, "-s", SERVICE_NAME, "-U", "-w",
114
+ ], "%s\n%s\n" % (source, source), "macOS Keychain", "write")
115
+ if result.returncode != 0:
116
+ raise _command_failure("macOS Keychain", "write", result)
117
+
118
+ def _remove_account(self, account):
119
+ result = _run(self._runner, [
120
+ MACOS_SECURITY_PATH, "delete-generic-password",
121
+ "-a", account, "-s", SERVICE_NAME,
122
+ ], "", "macOS Keychain", "remove")
123
+ if result.returncode not in (0, MACOS_ITEM_NOT_FOUND):
124
+ raise _command_failure("macOS Keychain", "remove", result)
125
+
126
+ def read(self, name):
127
+ source = self._read_source(self._account(name))
128
+ return None if source is None else _parse_record(name, source)
129
+
130
+ def write(self, name, value):
131
+ _validate_record(name, value)
132
+ self._write_source(
133
+ self._account(name), json.dumps(value, separators=(",", ":")),
134
+ )
135
+ if self.read(name) != value:
136
+ raise SystemCredentialStoreError(
137
+ "macOS Keychain %s write verification failed" % name,
138
+ )
139
+
140
+ def remove(self, name):
141
+ self._remove_account(self._account(name))
142
+
143
+ def verify_availability(self):
144
+ account = "%s:probe:%s" % (self._namespace, uuid.uuid4())
145
+ expected = _probe_value()
146
+ _verify_round_trip(
147
+ lambda source: self._write_source(account, source),
148
+ lambda: self._read_source(account),
149
+ lambda: self._remove_account(account),
150
+ expected,
151
+ )
152
+
153
+ backend = "macos-keychain"
154
+
155
+
156
+ class LinuxSecretServiceStore:
157
+ def __init__(self, namespace, command, runner=run_credential_command):
158
+ self._namespace = namespace
159
+ self._command = command
160
+ self._runner = runner
161
+
162
+ def _attributes(self, name):
163
+ _validate_record_name(name)
164
+ return [
165
+ "application", APPLICATION_NAME,
166
+ "origin", self._namespace,
167
+ "record", name,
168
+ ]
169
+
170
+ def _lookup_source(self, attributes):
171
+ result = _run(
172
+ self._runner, [self._command, "lookup"] + attributes,
173
+ "", "Linux Secret Service", "read",
174
+ )
175
+ missing = (
176
+ result.returncode == 1
177
+ and not result.stdout.strip()
178
+ and not result.stderr.strip()
179
+ )
180
+ if missing:
181
+ return None
182
+ if result.returncode != 0:
183
+ raise _command_failure("Linux Secret Service", "read", result)
184
+ return result.stdout.strip()
185
+
186
+ def _write_source(self, attributes, source):
187
+ result = _run(self._runner, [
188
+ self._command, "store", "--label=%s" % SERVICE_NAME,
189
+ ] + attributes, source + "\n", "Linux Secret Service", "write")
190
+ if result.returncode != 0:
191
+ raise _command_failure("Linux Secret Service", "write", result)
192
+
193
+ def _remove_attributes(self, attributes):
194
+ if self._lookup_source(attributes) is None:
195
+ return
196
+ result = _run(
197
+ self._runner, [self._command, "clear"] + attributes,
198
+ "", "Linux Secret Service", "remove",
199
+ )
200
+ if result.returncode != 0:
201
+ raise _command_failure("Linux Secret Service", "remove", result)
202
+
203
+ def read(self, name):
204
+ source = self._lookup_source(self._attributes(name))
205
+ return None if source is None else _parse_record(name, source)
206
+
207
+ def write(self, name, value):
208
+ _validate_record(name, value)
209
+ self._write_source(
210
+ self._attributes(name), json.dumps(value, separators=(",", ":")),
211
+ )
212
+ if self.read(name) != value:
213
+ raise SystemCredentialStoreError(
214
+ "Linux Secret Service %s write verification failed" % name,
215
+ )
216
+
217
+ def remove(self, name):
218
+ self._remove_attributes(self._attributes(name))
219
+
220
+ def verify_availability(self):
221
+ attributes = [
222
+ "application", APPLICATION_NAME,
223
+ "origin", self._namespace,
224
+ "record", "probe-%s" % uuid.uuid4(),
225
+ ]
226
+ expected = _probe_value()
227
+ _verify_round_trip(
228
+ lambda source: self._write_source(attributes, source),
229
+ lambda: self._lookup_source(attributes),
230
+ lambda: self._remove_attributes(attributes),
231
+ expected,
232
+ )
233
+
234
+ backend = "linux-secret-service"
235
+
236
+
237
+ def resolve_system_credential_store(
238
+ platform, namespace, env=None, *, runner=run_credential_command):
239
+ if platform == "darwin":
240
+ if not os.path.isfile(MACOS_SECURITY_PATH):
241
+ return None
242
+ if not os.access(MACOS_SECURITY_PATH, os.X_OK):
243
+ return None
244
+ return MacOSCredentialStore(namespace, runner)
245
+ if platform == "linux":
246
+ environment = os.environ if env is None else env
247
+ command = shutil.which("secret-tool", path=environment.get("PATH"))
248
+ if command:
249
+ return LinuxSecretServiceStore(namespace, command, runner)
250
+ return None
@@ -2,7 +2,7 @@ import { randomUUID } from 'node:crypto';
2
2
  import { ActionState, serializeFailure } from './action-errors.js';
3
3
  import { createActionContext } from './action-context.js';
4
4
  import { validateActionRequest } from './action-validation.js';
5
- import { ensureAgent, getMyAgent, searchAgents } from './agent-actions.js';
5
+ import { ensureAgent, getMyAgent, searchAgents, updateAgent } from './agent-actions.js';
6
6
  import { contactAgent } from './contact-action.js';
7
7
  import { appeal, blockAgent, report, unblockAgent } from './governance-actions.js';
8
8
  import {
@@ -12,12 +12,19 @@ import {
12
12
  reply,
13
13
  withdrawMessage,
14
14
  } from './messaging-actions.js';
15
+ import {
16
+ deleteMemory,
17
+ getMemorySettings,
18
+ listMemories,
19
+ proposeMemory,
20
+ } from './memory-actions.js';
15
21
 
16
22
  const ACTIONS = Object.freeze({
17
23
  appeal,
18
24
  block_agent: blockAgent,
19
25
  contact_agent: contactAgent,
20
26
  ensure_agent: ensureAgent,
27
+ update_agent: updateAgent,
21
28
  get_conversation: getConversation,
22
29
  get_my_agent: getMyAgent,
23
30
  list_inbox: listInbox,
@@ -27,6 +34,10 @@ const ACTIONS = Object.freeze({
27
34
  search_agents: searchAgents,
28
35
  unblock_agent: unblockAgent,
29
36
  withdraw_message: withdrawMessage,
37
+ delete_memory: deleteMemory,
38
+ get_memory_settings: getMemorySettings,
39
+ list_memories: listMemories,
40
+ propose_memory: proposeMemory,
30
41
  });
31
42
 
32
43
  function canonicalize(value) {
@@ -5,6 +5,7 @@ export const OPERATIONS = Object.freeze([
5
5
  'search_agents',
6
6
  'get_my_agent',
7
7
  'ensure_agent',
8
+ 'update_agent',
8
9
  'contact_agent',
9
10
  'list_inbox',
10
11
  'get_conversation',
@@ -15,6 +16,10 @@ export const OPERATIONS = Object.freeze([
15
16
  'unblock_agent',
16
17
  'report',
17
18
  'appeal',
19
+ 'get_memory_settings',
20
+ 'propose_memory',
21
+ 'list_memories',
22
+ 'delete_memory',
18
23
  ]);
19
24
 
20
25
  const OPERATION_SET = new Set(OPERATIONS);