agent-sin 0.1.0

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 (150) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/LICENSE +21 -0
  3. package/README.md +81 -0
  4. package/assets/logo.png +0 -0
  5. package/builtin-skills/_shared/_models_lib.py +227 -0
  6. package/builtin-skills/_shared/_profile_lib.py +98 -0
  7. package/builtin-skills/_shared/_schedules_lib.py +313 -0
  8. package/builtin-skills/_shared/_skill_settings_lib.py +153 -0
  9. package/builtin-skills/_shared/i18n.py +26 -0
  10. package/builtin-skills/memo-delete/main.py +155 -0
  11. package/builtin-skills/memo-delete/skill.yaml +57 -0
  12. package/builtin-skills/memo-index/main.py +178 -0
  13. package/builtin-skills/memo-index/skill.yaml +53 -0
  14. package/builtin-skills/memo-save/README.md +5 -0
  15. package/builtin-skills/memo-save/main.py +74 -0
  16. package/builtin-skills/memo-save/skill.yaml +52 -0
  17. package/builtin-skills/memo-search/README.md +10 -0
  18. package/builtin-skills/memo-search/main.py +97 -0
  19. package/builtin-skills/memo-search/skill.yaml +51 -0
  20. package/builtin-skills/memo-vector-search/main.py +121 -0
  21. package/builtin-skills/memo-vector-search/skill.yaml +53 -0
  22. package/builtin-skills/model-add/main.py +180 -0
  23. package/builtin-skills/model-add/skill.yaml +112 -0
  24. package/builtin-skills/model-list/main.py +93 -0
  25. package/builtin-skills/model-list/skill.yaml +48 -0
  26. package/builtin-skills/model-set/main.py +123 -0
  27. package/builtin-skills/model-set/skill.yaml +69 -0
  28. package/builtin-skills/profile-delete/_profile_lib.py +98 -0
  29. package/builtin-skills/profile-delete/main.py +98 -0
  30. package/builtin-skills/profile-delete/skill.yaml +64 -0
  31. package/builtin-skills/profile-edit/_profile_lib.py +98 -0
  32. package/builtin-skills/profile-edit/main.py +97 -0
  33. package/builtin-skills/profile-edit/skill.yaml +72 -0
  34. package/builtin-skills/profile-save/main.py +52 -0
  35. package/builtin-skills/profile-save/skill.yaml +69 -0
  36. package/builtin-skills/schedule-add/_schedules_lib.py +303 -0
  37. package/builtin-skills/schedule-add/main.py +137 -0
  38. package/builtin-skills/schedule-add/skill.yaml +94 -0
  39. package/builtin-skills/schedule-list/_schedules_lib.py +303 -0
  40. package/builtin-skills/schedule-list/main.py +86 -0
  41. package/builtin-skills/schedule-list/skill.yaml +45 -0
  42. package/builtin-skills/schedule-remove/_schedules_lib.py +303 -0
  43. package/builtin-skills/schedule-remove/main.py +69 -0
  44. package/builtin-skills/schedule-remove/skill.yaml +49 -0
  45. package/builtin-skills/schedule-toggle/_schedules_lib.py +303 -0
  46. package/builtin-skills/schedule-toggle/main.py +78 -0
  47. package/builtin-skills/schedule-toggle/skill.yaml +61 -0
  48. package/builtin-skills/skills-disable/main.py +63 -0
  49. package/builtin-skills/skills-disable/skill.yaml +52 -0
  50. package/builtin-skills/skills-enable/main.py +62 -0
  51. package/builtin-skills/skills-enable/skill.yaml +51 -0
  52. package/builtin-skills/todo-add/main.py +68 -0
  53. package/builtin-skills/todo-add/skill.yaml +53 -0
  54. package/builtin-skills/todo-delete/main.py +65 -0
  55. package/builtin-skills/todo-delete/skill.yaml +47 -0
  56. package/builtin-skills/todo-done/main.py +75 -0
  57. package/builtin-skills/todo-done/skill.yaml +47 -0
  58. package/builtin-skills/todo-list/main.py +91 -0
  59. package/builtin-skills/todo-list/skill.yaml +48 -0
  60. package/builtin-skills/todo-tick/main.py +125 -0
  61. package/builtin-skills/todo-tick/skill.yaml +48 -0
  62. package/dist/builder/build-action-classifier.d.ts +18 -0
  63. package/dist/builder/build-action-classifier.js +142 -0
  64. package/dist/builder/build-commands.d.ts +19 -0
  65. package/dist/builder/build-commands.js +133 -0
  66. package/dist/builder/build-flow.d.ts +72 -0
  67. package/dist/builder/build-flow.js +416 -0
  68. package/dist/builder/builder-session.d.ts +117 -0
  69. package/dist/builder/builder-session.js +1129 -0
  70. package/dist/builder/conversation-router.d.ts +22 -0
  71. package/dist/builder/conversation-router.js +69 -0
  72. package/dist/builder/intent-runtime-store.d.ts +7 -0
  73. package/dist/builder/intent-runtime-store.js +60 -0
  74. package/dist/builder/progress-format.d.ts +7 -0
  75. package/dist/builder/progress-format.js +46 -0
  76. package/dist/cli/index.d.ts +2 -0
  77. package/dist/cli/index.js +2835 -0
  78. package/dist/cli/spinner.d.ts +30 -0
  79. package/dist/cli/spinner.js +164 -0
  80. package/dist/core/ai-provider.d.ts +75 -0
  81. package/dist/core/ai-provider.js +678 -0
  82. package/dist/core/builtin-skills.d.ts +27 -0
  83. package/dist/core/builtin-skills.js +120 -0
  84. package/dist/core/chat-engine.d.ts +70 -0
  85. package/dist/core/chat-engine.js +812 -0
  86. package/dist/core/config.d.ts +127 -0
  87. package/dist/core/config.js +1379 -0
  88. package/dist/core/daily-memory-promotion.d.ts +21 -0
  89. package/dist/core/daily-memory-promotion.js +422 -0
  90. package/dist/core/i18n.d.ts +23 -0
  91. package/dist/core/i18n.js +167 -0
  92. package/dist/core/info-lines.d.ts +5 -0
  93. package/dist/core/info-lines.js +39 -0
  94. package/dist/core/input-schema.d.ts +2 -0
  95. package/dist/core/input-schema.js +156 -0
  96. package/dist/core/intent-router.d.ts +27 -0
  97. package/dist/core/intent-router.js +160 -0
  98. package/dist/core/logger.d.ts +60 -0
  99. package/dist/core/logger.js +240 -0
  100. package/dist/core/memory.d.ts +10 -0
  101. package/dist/core/memory.js +72 -0
  102. package/dist/core/message-utils.d.ts +13 -0
  103. package/dist/core/message-utils.js +104 -0
  104. package/dist/core/notifier.d.ts +17 -0
  105. package/dist/core/notifier.js +424 -0
  106. package/dist/core/output-writer.d.ts +13 -0
  107. package/dist/core/output-writer.js +100 -0
  108. package/dist/core/plan-decision.d.ts +16 -0
  109. package/dist/core/plan-decision.js +88 -0
  110. package/dist/core/profile-memory.d.ts +17 -0
  111. package/dist/core/profile-memory.js +142 -0
  112. package/dist/core/runtime.d.ts +50 -0
  113. package/dist/core/runtime.js +187 -0
  114. package/dist/core/scheduler.d.ts +28 -0
  115. package/dist/core/scheduler.js +155 -0
  116. package/dist/core/secrets.d.ts +31 -0
  117. package/dist/core/secrets.js +214 -0
  118. package/dist/core/service.d.ts +35 -0
  119. package/dist/core/service.js +479 -0
  120. package/dist/core/skill-planner.d.ts +24 -0
  121. package/dist/core/skill-planner.js +100 -0
  122. package/dist/core/skill-registry.d.ts +98 -0
  123. package/dist/core/skill-registry.js +319 -0
  124. package/dist/core/skill-scaffold.d.ts +33 -0
  125. package/dist/core/skill-scaffold.js +256 -0
  126. package/dist/core/skill-settings.d.ts +11 -0
  127. package/dist/core/skill-settings.js +63 -0
  128. package/dist/core/transfer.d.ts +31 -0
  129. package/dist/core/transfer.js +270 -0
  130. package/dist/core/update-notifier.d.ts +2 -0
  131. package/dist/core/update-notifier.js +140 -0
  132. package/dist/discord/bot.d.ts +96 -0
  133. package/dist/discord/bot.js +2424 -0
  134. package/dist/runtimes/codex-app-server.d.ts +53 -0
  135. package/dist/runtimes/codex-app-server.js +305 -0
  136. package/dist/runtimes/python-runner.d.ts +7 -0
  137. package/dist/runtimes/python-runner.js +302 -0
  138. package/dist/runtimes/typescript-runner.d.ts +5 -0
  139. package/dist/runtimes/typescript-runner.js +172 -0
  140. package/dist/skills-sdk/types.d.ts +38 -0
  141. package/dist/skills-sdk/types.js +1 -0
  142. package/dist/telegram/bot.d.ts +94 -0
  143. package/dist/telegram/bot.js +1219 -0
  144. package/install.ps1 +132 -0
  145. package/install.sh +130 -0
  146. package/package.json +60 -0
  147. package/templates/skill-python/main.py +74 -0
  148. package/templates/skill-python/skill.yaml +48 -0
  149. package/templates/skill-typescript/main.ts +87 -0
  150. package/templates/skill-typescript/skill.yaml +42 -0
@@ -0,0 +1,98 @@
1
+ """Shared helpers for profile-edit / profile-delete skills.
2
+
3
+ soul.md / user.md / memory.md は profile-save が `\n## <timestamp>\n\n<text>\n`
4
+ 形式で追記する。これを「ヘッダー = '## ...' を境にしたエントリ列」として
5
+ 扱うためのパーサと再シリアライズを提供する。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import re
12
+ import tempfile
13
+ from typing import List, Optional, Tuple
14
+
15
+
16
+ _HEADER_RE = re.compile(r"^##\s+(.+?)\s*$")
17
+
18
+
19
+ def profile_file(workspace: str, target: str) -> str:
20
+ return os.path.join(workspace, "memory", "profile", f"{target}.md")
21
+
22
+
23
+ def parse_profile(raw: str) -> Tuple[str, List[dict]]:
24
+ """Split into (preamble, entries[{ timestamp, text }]).
25
+
26
+ preamble は最初の '## ' より前の部分(ファイル冒頭のコメント等)。
27
+ 各 entry の `text` は本文のみ(前後の改行は trim 済み)。
28
+ """
29
+ lines = raw.splitlines(keepends=False)
30
+ pre: List[str] = []
31
+ entries: List[dict] = []
32
+ current: Optional[dict] = None
33
+ for line in lines:
34
+ m = _HEADER_RE.match(line)
35
+ if m:
36
+ if current is not None:
37
+ current["text"] = "\n".join(current["_body"]).strip()
38
+ del current["_body"]
39
+ entries.append(current)
40
+ current = {"timestamp": m.group(1).strip(), "_body": []}
41
+ continue
42
+ if current is None:
43
+ pre.append(line)
44
+ else:
45
+ current["_body"].append(line)
46
+ if current is not None:
47
+ current["text"] = "\n".join(current["_body"]).strip()
48
+ del current["_body"]
49
+ entries.append(current)
50
+ preamble = "\n".join(pre).rstrip("\n")
51
+ return preamble, entries
52
+
53
+
54
+ def serialize_profile(preamble: str, entries: List[dict]) -> str:
55
+ out: List[str] = []
56
+ if preamble:
57
+ out.append(preamble)
58
+ for entry in entries:
59
+ out.append("")
60
+ out.append(f"## {entry['timestamp']}")
61
+ out.append("")
62
+ out.append(entry["text"])
63
+ text = "\n".join(out)
64
+ if not text.endswith("\n"):
65
+ text += "\n"
66
+ return text
67
+
68
+
69
+ def write_atomic(path: str, content: str) -> None:
70
+ os.makedirs(os.path.dirname(path), exist_ok=True)
71
+ fd, tmp = tempfile.mkstemp(
72
+ prefix=".profile.", suffix=".md.tmp", dir=os.path.dirname(path),
73
+ )
74
+ try:
75
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
76
+ f.write(content)
77
+ os.replace(tmp, path)
78
+ except Exception:
79
+ try:
80
+ os.unlink(tmp)
81
+ except Exception:
82
+ pass
83
+ raise
84
+
85
+
86
+ def find_entry_index(entries: List[dict], *, index: Optional[int], timestamp: Optional[str]) -> int:
87
+ if timestamp:
88
+ for i, entry in enumerate(entries):
89
+ if entry["timestamp"] == timestamp:
90
+ return i
91
+ raise LookupError(f'timestamp "{timestamp}" にマッチするエントリがありません')
92
+ if index is not None:
93
+ if index < 1 or index > len(entries):
94
+ raise LookupError(
95
+ f"index {index} は範囲外です (entries={len(entries)})",
96
+ )
97
+ return index - 1
98
+ raise LookupError("index か timestamp のどちらかを指定してください")
@@ -0,0 +1,98 @@
1
+ """Builtin: profile-delete
2
+
3
+ soul.md / user.md / memory.md の既存エントリを削除する。
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ import sys
10
+
11
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_shared"))
12
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
13
+ from i18n import localizer # noqa: E402
14
+ from _profile_lib import ( # noqa: E402
15
+ find_entry_index,
16
+ parse_profile,
17
+ profile_file,
18
+ serialize_profile,
19
+ write_atomic,
20
+ )
21
+
22
+
23
+ VALID_TARGETS = {"soul", "user", "memory"}
24
+
25
+
26
+ async def run(ctx, input):
27
+ loc = localizer(input)
28
+ args = input.get("args", {}) or {}
29
+ workspace = input.get("sources", {}).get("workspace", "")
30
+ if not workspace:
31
+ return _err(loc.t("Workspace unavailable", "ワークスペース不明"), loc.t("The workspace path is unavailable.", "workspace パスが取得できません"))
32
+
33
+ target = str(args.get("target", "")).strip()
34
+ index = args.get("index")
35
+ timestamp = args.get("timestamp")
36
+ timestamp = str(timestamp).strip() if timestamp else None
37
+
38
+ if target not in VALID_TARGETS:
39
+ return _err(loc.t("Invalid target", "対象不正"), loc.t("target must be soul, user, or memory.", "target は soul / user / memory のいずれかを指定してください"))
40
+ if index is not None and not isinstance(index, int):
41
+ return _err(loc.t("Invalid index", "index不正"), loc.t("index must be a positive integer.", "index は正の整数で指定してください"))
42
+ if index is None and not timestamp:
43
+ return _err(loc.t("Cannot identify entry", "特定不可"), loc.t("Specify either index or timestamp.", "index か timestamp のどちらかを指定してください"))
44
+
45
+ path = profile_file(workspace, target)
46
+ if not os.path.exists(path):
47
+ return _err(loc.t("File not found", "対象ファイルなし"), loc.t(f"{target}.md does not exist.", f"{target}.md が存在しません"))
48
+
49
+ with open(path, "r", encoding="utf-8") as f:
50
+ raw = f.read()
51
+ preamble, entries = parse_profile(raw)
52
+
53
+ if not entries:
54
+ return _err(loc.t("No entries", "エントリなし"), loc.t(f"{target}.md has no entries to delete.", f"{target}.md に削除対象のエントリがありません"))
55
+
56
+ try:
57
+ i = find_entry_index(entries, index=index, timestamp=timestamp)
58
+ except LookupError as e:
59
+ return _err(loc.t("Cannot identify entry", "特定不可"), str(e))
60
+
61
+ removed = entries.pop(i)
62
+
63
+ try:
64
+ write_atomic(path, serialize_profile(preamble, entries))
65
+ except Exception as e:
66
+ return _err(loc.t("Save failed", "保存失敗"), loc.t(f"Failed to write {target}.md: {e}", f"{target}.md への書き込みに失敗しました: {e}"))
67
+
68
+ ctx.log.info(f"profile-delete: {target}.md index={i + 1} removed")
69
+
70
+ preview = removed["text"]
71
+ if len(preview) > 60:
72
+ preview = preview[:57] + "..."
73
+ return {
74
+ "status": "ok",
75
+ "title": loc.t("Deleted", "削除"),
76
+ "summary": loc.t(f"Deleted entry {i + 1} from {target}.md: {preview}", f"{target}.md の {i + 1}番目を削除しました: {preview}"),
77
+ "outputs": {},
78
+ "data": {
79
+ "target": target,
80
+ "index": i + 1,
81
+ "timestamp": removed["timestamp"],
82
+ "removed": removed,
83
+ "remaining": len(entries),
84
+ "path": path,
85
+ },
86
+ "suggestions": [],
87
+ }
88
+
89
+
90
+ def _err(title, summary):
91
+ return {
92
+ "status": "error",
93
+ "title": title,
94
+ "summary": summary,
95
+ "outputs": {},
96
+ "data": {},
97
+ "suggestions": [],
98
+ }
@@ -0,0 +1,64 @@
1
+ # Builtin: profile-delete
2
+ # soul.md / user.md / memory.md の既存エントリを削除する。
3
+
4
+ id: profile-delete
5
+ name: Profile Delete
6
+ name_i18n:
7
+ en: Profile Delete
8
+ ja: プロフィール削除
9
+ description: 長期プロフィールのエントリを削除する
10
+ description_i18n:
11
+ en: Delete a long-term profile entry
12
+ ja: 長期プロフィールのエントリを削除する
13
+ runtime: python
14
+ output_mode: raw
15
+ side_effect: true
16
+
17
+ invocation:
18
+ command: profile.delete
19
+ phrases:
20
+ - プロフィールを消して
21
+ - soul.mdから消して
22
+ - user.mdから消して
23
+ - memory.mdから消して
24
+ - 長期メモを削除
25
+ phrases_i18n:
26
+ en:
27
+ - delete profile
28
+ - delete from soul.md
29
+ - delete from user.md
30
+ - delete from memory.md
31
+ - delete long-term memory
32
+ ja:
33
+ - プロフィールを消して
34
+ - soul.mdから消して
35
+ - user.mdから消して
36
+ - memory.mdから消して
37
+ - 長期メモを削除
38
+
39
+ input:
40
+ schema:
41
+ type: object
42
+ additionalProperties: false
43
+ properties:
44
+ target:
45
+ type: string
46
+ enum:
47
+ - soul
48
+ - user
49
+ - memory
50
+ index:
51
+ type: integer
52
+ minimum: 1
53
+ description: 上から数えた1始まりのエントリ番号
54
+ description_i18n:
55
+ en: 1-based entry number counted from the top
56
+ ja: 上から数えた1始まりのエントリ番号
57
+ timestamp:
58
+ type: string
59
+ description: エントリの '## <timestamp>' ヘッダ値で特定する場合に指定
60
+ description_i18n:
61
+ en: Use the entry's '## <timestamp>' header value to identify it
62
+ ja: エントリの '## <timestamp>' ヘッダ値で特定する場合に指定
63
+ required:
64
+ - target
@@ -0,0 +1,98 @@
1
+ """Shared helpers for profile-edit / profile-delete skills.
2
+
3
+ soul.md / user.md / memory.md は profile-save が `\n## <timestamp>\n\n<text>\n`
4
+ 形式で追記する。これを「ヘッダー = '## ...' を境にしたエントリ列」として
5
+ 扱うためのパーサと再シリアライズを提供する。
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import os
11
+ import re
12
+ import tempfile
13
+ from typing import List, Optional, Tuple
14
+
15
+
16
+ _HEADER_RE = re.compile(r"^##\s+(.+?)\s*$")
17
+
18
+
19
+ def profile_file(workspace: str, target: str) -> str:
20
+ return os.path.join(workspace, "memory", "profile", f"{target}.md")
21
+
22
+
23
+ def parse_profile(raw: str) -> Tuple[str, List[dict]]:
24
+ """Split into (preamble, entries[{ timestamp, text }]).
25
+
26
+ preamble は最初の '## ' より前の部分(ファイル冒頭のコメント等)。
27
+ 各 entry の `text` は本文のみ(前後の改行は trim 済み)。
28
+ """
29
+ lines = raw.splitlines(keepends=False)
30
+ pre: List[str] = []
31
+ entries: List[dict] = []
32
+ current: Optional[dict] = None
33
+ for line in lines:
34
+ m = _HEADER_RE.match(line)
35
+ if m:
36
+ if current is not None:
37
+ current["text"] = "\n".join(current["_body"]).strip()
38
+ del current["_body"]
39
+ entries.append(current)
40
+ current = {"timestamp": m.group(1).strip(), "_body": []}
41
+ continue
42
+ if current is None:
43
+ pre.append(line)
44
+ else:
45
+ current["_body"].append(line)
46
+ if current is not None:
47
+ current["text"] = "\n".join(current["_body"]).strip()
48
+ del current["_body"]
49
+ entries.append(current)
50
+ preamble = "\n".join(pre).rstrip("\n")
51
+ return preamble, entries
52
+
53
+
54
+ def serialize_profile(preamble: str, entries: List[dict]) -> str:
55
+ out: List[str] = []
56
+ if preamble:
57
+ out.append(preamble)
58
+ for entry in entries:
59
+ out.append("")
60
+ out.append(f"## {entry['timestamp']}")
61
+ out.append("")
62
+ out.append(entry["text"])
63
+ text = "\n".join(out)
64
+ if not text.endswith("\n"):
65
+ text += "\n"
66
+ return text
67
+
68
+
69
+ def write_atomic(path: str, content: str) -> None:
70
+ os.makedirs(os.path.dirname(path), exist_ok=True)
71
+ fd, tmp = tempfile.mkstemp(
72
+ prefix=".profile.", suffix=".md.tmp", dir=os.path.dirname(path),
73
+ )
74
+ try:
75
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
76
+ f.write(content)
77
+ os.replace(tmp, path)
78
+ except Exception:
79
+ try:
80
+ os.unlink(tmp)
81
+ except Exception:
82
+ pass
83
+ raise
84
+
85
+
86
+ def find_entry_index(entries: List[dict], *, index: Optional[int], timestamp: Optional[str]) -> int:
87
+ if timestamp:
88
+ for i, entry in enumerate(entries):
89
+ if entry["timestamp"] == timestamp:
90
+ return i
91
+ raise LookupError(f'timestamp "{timestamp}" にマッチするエントリがありません')
92
+ if index is not None:
93
+ if index < 1 or index > len(entries):
94
+ raise LookupError(
95
+ f"index {index} は範囲外です (entries={len(entries)})",
96
+ )
97
+ return index - 1
98
+ raise LookupError("index か timestamp のどちらかを指定してください")
@@ -0,0 +1,97 @@
1
+ """Builtin: profile-edit
2
+
3
+ soul.md / user.md / memory.md の既存エントリ本文を置き換える。
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import os
9
+ import sys
10
+
11
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_shared"))
12
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
13
+ from i18n import localizer # noqa: E402
14
+ from _profile_lib import ( # noqa: E402
15
+ find_entry_index,
16
+ parse_profile,
17
+ profile_file,
18
+ serialize_profile,
19
+ write_atomic,
20
+ )
21
+
22
+
23
+ VALID_TARGETS = {"soul", "user", "memory"}
24
+
25
+
26
+ async def run(ctx, input):
27
+ loc = localizer(input)
28
+ args = input.get("args", {}) or {}
29
+ workspace = input.get("sources", {}).get("workspace", "")
30
+ if not workspace:
31
+ return _err(loc.t("Workspace unavailable", "ワークスペース不明"), loc.t("The workspace path is unavailable.", "workspace パスが取得できません"))
32
+
33
+ target = str(args.get("target", "")).strip()
34
+ text = str(args.get("text", "")).strip()
35
+ index = args.get("index")
36
+ timestamp = args.get("timestamp")
37
+ timestamp = str(timestamp).strip() if timestamp else None
38
+
39
+ if target not in VALID_TARGETS:
40
+ return _err(loc.t("Invalid target", "対象不正"), loc.t("target must be soul, user, or memory.", "target は soul / user / memory のいずれかを指定してください"))
41
+ if not text:
42
+ return _err(loc.t("No text", "本文なし"), loc.t("Specify the replacement text.", "置き換える本文を指定してください"))
43
+ if index is not None and not isinstance(index, int):
44
+ return _err(loc.t("Invalid index", "index不正"), loc.t("index must be a positive integer.", "index は正の整数で指定してください"))
45
+
46
+ path = profile_file(workspace, target)
47
+ if not os.path.exists(path):
48
+ return _err(loc.t("File not found", "対象ファイルなし"), loc.t(f"{target}.md does not exist.", f"{target}.md が存在しません"))
49
+
50
+ with open(path, "r", encoding="utf-8") as f:
51
+ raw = f.read()
52
+ preamble, entries = parse_profile(raw)
53
+
54
+ if not entries:
55
+ return _err(loc.t("No entries", "エントリなし"), loc.t(f"{target}.md has no entries to edit.", f"{target}.md に編集対象のエントリがありません"))
56
+
57
+ try:
58
+ i = find_entry_index(entries, index=index, timestamp=timestamp)
59
+ except LookupError as e:
60
+ return _err(loc.t("Cannot identify entry", "特定不可"), str(e))
61
+
62
+ before = entries[i]["text"]
63
+ entries[i]["text"] = text
64
+
65
+ try:
66
+ write_atomic(path, serialize_profile(preamble, entries))
67
+ except Exception as e:
68
+ return _err(loc.t("Save failed", "保存失敗"), loc.t(f"Failed to write {target}.md: {e}", f"{target}.md への書き込みに失敗しました: {e}"))
69
+
70
+ ctx.log.info(f"profile-edit: {target}.md index={i + 1} updated")
71
+
72
+ return {
73
+ "status": "ok",
74
+ "title": loc.t("Updated", "更新"),
75
+ "summary": loc.t(f"Updated entry {i + 1} in {target}.md", f"{target}.md の {i + 1}番目を更新しました"),
76
+ "outputs": {},
77
+ "data": {
78
+ "target": target,
79
+ "index": i + 1,
80
+ "timestamp": entries[i]["timestamp"],
81
+ "before": before,
82
+ "after": text,
83
+ "path": path,
84
+ },
85
+ "suggestions": [],
86
+ }
87
+
88
+
89
+ def _err(title, summary):
90
+ return {
91
+ "status": "error",
92
+ "title": title,
93
+ "summary": summary,
94
+ "outputs": {},
95
+ "data": {},
96
+ "suggestions": [],
97
+ }
@@ -0,0 +1,72 @@
1
+ # Builtin: profile-edit
2
+ # soul.md / user.md / memory.md の既存エントリを書き換える。
3
+
4
+ id: profile-edit
5
+ name: Profile Edit
6
+ name_i18n:
7
+ en: Profile Edit
8
+ ja: プロフィール編集
9
+ description: 長期プロフィールの既存エントリを編集する
10
+ description_i18n:
11
+ en: Edit an existing long-term profile entry
12
+ ja: 長期プロフィールの既存エントリを編集する
13
+ runtime: python
14
+ output_mode: raw
15
+ side_effect: true
16
+
17
+ invocation:
18
+ command: profile.edit
19
+ phrases:
20
+ - プロフィールを修正
21
+ - soul.mdを編集
22
+ - user.mdを編集
23
+ - memory.mdを編集
24
+ - 長期メモを直して
25
+ phrases_i18n:
26
+ en:
27
+ - edit profile
28
+ - edit soul.md
29
+ - edit user.md
30
+ - edit memory.md
31
+ - fix long-term memory
32
+ ja:
33
+ - プロフィールを修正
34
+ - soul.mdを編集
35
+ - user.mdを編集
36
+ - memory.mdを編集
37
+ - 長期メモを直して
38
+
39
+ input:
40
+ schema:
41
+ type: object
42
+ additionalProperties: false
43
+ properties:
44
+ target:
45
+ type: string
46
+ enum:
47
+ - soul
48
+ - user
49
+ - memory
50
+ index:
51
+ type: integer
52
+ minimum: 1
53
+ description: 上から数えた1始まりのエントリ番号
54
+ description_i18n:
55
+ en: 1-based entry number counted from the top
56
+ ja: 上から数えた1始まりのエントリ番号
57
+ timestamp:
58
+ type: string
59
+ description: エントリの '## <timestamp>' ヘッダ値で特定する場合に指定
60
+ description_i18n:
61
+ en: Use the entry's '## <timestamp>' header value to identify it
62
+ ja: エントリの '## <timestamp>' ヘッダ値で特定する場合に指定
63
+ text:
64
+ type: string
65
+ minLength: 1
66
+ description: 置き換える本文
67
+ description_i18n:
68
+ en: Replacement text
69
+ ja: 置き換える本文
70
+ required:
71
+ - target
72
+ - text
@@ -0,0 +1,52 @@
1
+ """Builtin: profile-save
2
+
3
+ soul.md / user.md / memory.md に長期プロフィールを追記する。
4
+ Runtime が outputs の保存先に従って書き込むため、このスキル自身は content を返すだけ。
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import os
10
+ import sys
11
+
12
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "_shared"))
13
+ from i18n import localizer # noqa: E402
14
+
15
+
16
+ async def run(ctx, input):
17
+ loc = localizer(input)
18
+ args = input.get("args", {})
19
+ target = str(args.get("target", "")).strip()
20
+ text = str(args.get("text", "")).strip()
21
+
22
+ if target not in {"soul", "user", "memory"}:
23
+ return result("error", loc.t("Cannot save", "保存できません"), loc.t("target must be soul, user, or memory.", "target は soul / user / memory のいずれかを指定してください"), {})
24
+ if not text:
25
+ ctx.log.warn("profile-save: empty text, skipping")
26
+ return result("skipped", loc.t("Nothing saved", "保存なし"), loc.t("There is no text to save.", "保存する本文がありません"), {})
27
+
28
+ timestamp = input.get("trigger", {}).get("time") or ctx.now()
29
+ content = f"\n## {timestamp}\n\n{text}\n"
30
+ ctx.log.info(f"profile-save: saving {len(text)} chars to {target}.md")
31
+ return result(
32
+ "ok",
33
+ loc.t("Saved", "保存しました"),
34
+ loc.t(f"Saved to {target}.md", f"{target}.md に保存しました"),
35
+ {
36
+ target: {
37
+ "content": content,
38
+ "frontmatter": {},
39
+ }
40
+ },
41
+ )
42
+
43
+
44
+ def result(status, title, summary, outputs):
45
+ return {
46
+ "status": status,
47
+ "title": title,
48
+ "summary": summary,
49
+ "outputs": outputs,
50
+ "data": {},
51
+ "suggestions": [],
52
+ }
@@ -0,0 +1,69 @@
1
+ # Builtin: profile-save
2
+ # soul.md / user.md / memory.md に、ユーザーが明示した長期プロフィールを追記する。
3
+
4
+ id: profile-save
5
+ name: Profile Save
6
+ name_i18n:
7
+ en: Profile Save
8
+ ja: プロフィール保存
9
+ description: soul.md、user.md、memory.mdへ長期プロフィールを保存する
10
+ description_i18n:
11
+ en: Save long-term profile entries to soul.md, user.md, or memory.md
12
+ ja: soul.md、user.md、memory.mdへ長期プロフィールを保存する
13
+ runtime: python
14
+ side_effect: true
15
+
16
+ invocation:
17
+ command: profile.save
18
+ phrases:
19
+ - soul.mdに書いて
20
+ - user.mdに書いて
21
+ - memory.mdに書いて
22
+ - 長期記憶に保存
23
+ - プロフィールに記録
24
+ phrases_i18n:
25
+ en:
26
+ - write to soul.md
27
+ - write to user.md
28
+ - write to memory.md
29
+ - save to long-term memory
30
+ - record profile
31
+ ja:
32
+ - soul.mdに書いて
33
+ - user.mdに書いて
34
+ - memory.mdに書いて
35
+ - 長期記憶に保存
36
+ - プロフィールに記録
37
+
38
+ input:
39
+ schema:
40
+ type: object
41
+ properties:
42
+ target:
43
+ type: string
44
+ enum:
45
+ - soul
46
+ - user
47
+ - memory
48
+ text:
49
+ type: string
50
+ required:
51
+ - target
52
+ - text
53
+
54
+ outputs:
55
+ - id: soul
56
+ type: markdown
57
+ path: memory/profile
58
+ filename: soul.md
59
+ append: true
60
+ - id: user
61
+ type: markdown
62
+ path: memory/profile
63
+ filename: user.md
64
+ append: true
65
+ - id: memory
66
+ type: markdown
67
+ path: memory/profile
68
+ filename: memory.md
69
+ append: true