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.
- package/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +81 -0
- package/assets/logo.png +0 -0
- package/builtin-skills/_shared/_models_lib.py +227 -0
- package/builtin-skills/_shared/_profile_lib.py +98 -0
- package/builtin-skills/_shared/_schedules_lib.py +313 -0
- package/builtin-skills/_shared/_skill_settings_lib.py +153 -0
- package/builtin-skills/_shared/i18n.py +26 -0
- package/builtin-skills/memo-delete/main.py +155 -0
- package/builtin-skills/memo-delete/skill.yaml +57 -0
- package/builtin-skills/memo-index/main.py +178 -0
- package/builtin-skills/memo-index/skill.yaml +53 -0
- package/builtin-skills/memo-save/README.md +5 -0
- package/builtin-skills/memo-save/main.py +74 -0
- package/builtin-skills/memo-save/skill.yaml +52 -0
- package/builtin-skills/memo-search/README.md +10 -0
- package/builtin-skills/memo-search/main.py +97 -0
- package/builtin-skills/memo-search/skill.yaml +51 -0
- package/builtin-skills/memo-vector-search/main.py +121 -0
- package/builtin-skills/memo-vector-search/skill.yaml +53 -0
- package/builtin-skills/model-add/main.py +180 -0
- package/builtin-skills/model-add/skill.yaml +112 -0
- package/builtin-skills/model-list/main.py +93 -0
- package/builtin-skills/model-list/skill.yaml +48 -0
- package/builtin-skills/model-set/main.py +123 -0
- package/builtin-skills/model-set/skill.yaml +69 -0
- package/builtin-skills/profile-delete/_profile_lib.py +98 -0
- package/builtin-skills/profile-delete/main.py +98 -0
- package/builtin-skills/profile-delete/skill.yaml +64 -0
- package/builtin-skills/profile-edit/_profile_lib.py +98 -0
- package/builtin-skills/profile-edit/main.py +97 -0
- package/builtin-skills/profile-edit/skill.yaml +72 -0
- package/builtin-skills/profile-save/main.py +52 -0
- package/builtin-skills/profile-save/skill.yaml +69 -0
- package/builtin-skills/schedule-add/_schedules_lib.py +303 -0
- package/builtin-skills/schedule-add/main.py +137 -0
- package/builtin-skills/schedule-add/skill.yaml +94 -0
- package/builtin-skills/schedule-list/_schedules_lib.py +303 -0
- package/builtin-skills/schedule-list/main.py +86 -0
- package/builtin-skills/schedule-list/skill.yaml +45 -0
- package/builtin-skills/schedule-remove/_schedules_lib.py +303 -0
- package/builtin-skills/schedule-remove/main.py +69 -0
- package/builtin-skills/schedule-remove/skill.yaml +49 -0
- package/builtin-skills/schedule-toggle/_schedules_lib.py +303 -0
- package/builtin-skills/schedule-toggle/main.py +78 -0
- package/builtin-skills/schedule-toggle/skill.yaml +61 -0
- package/builtin-skills/skills-disable/main.py +63 -0
- package/builtin-skills/skills-disable/skill.yaml +52 -0
- package/builtin-skills/skills-enable/main.py +62 -0
- package/builtin-skills/skills-enable/skill.yaml +51 -0
- package/builtin-skills/todo-add/main.py +68 -0
- package/builtin-skills/todo-add/skill.yaml +53 -0
- package/builtin-skills/todo-delete/main.py +65 -0
- package/builtin-skills/todo-delete/skill.yaml +47 -0
- package/builtin-skills/todo-done/main.py +75 -0
- package/builtin-skills/todo-done/skill.yaml +47 -0
- package/builtin-skills/todo-list/main.py +91 -0
- package/builtin-skills/todo-list/skill.yaml +48 -0
- package/builtin-skills/todo-tick/main.py +125 -0
- package/builtin-skills/todo-tick/skill.yaml +48 -0
- package/dist/builder/build-action-classifier.d.ts +18 -0
- package/dist/builder/build-action-classifier.js +142 -0
- package/dist/builder/build-commands.d.ts +19 -0
- package/dist/builder/build-commands.js +133 -0
- package/dist/builder/build-flow.d.ts +72 -0
- package/dist/builder/build-flow.js +416 -0
- package/dist/builder/builder-session.d.ts +117 -0
- package/dist/builder/builder-session.js +1129 -0
- package/dist/builder/conversation-router.d.ts +22 -0
- package/dist/builder/conversation-router.js +69 -0
- package/dist/builder/intent-runtime-store.d.ts +7 -0
- package/dist/builder/intent-runtime-store.js +60 -0
- package/dist/builder/progress-format.d.ts +7 -0
- package/dist/builder/progress-format.js +46 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +2835 -0
- package/dist/cli/spinner.d.ts +30 -0
- package/dist/cli/spinner.js +164 -0
- package/dist/core/ai-provider.d.ts +75 -0
- package/dist/core/ai-provider.js +678 -0
- package/dist/core/builtin-skills.d.ts +27 -0
- package/dist/core/builtin-skills.js +120 -0
- package/dist/core/chat-engine.d.ts +70 -0
- package/dist/core/chat-engine.js +812 -0
- package/dist/core/config.d.ts +127 -0
- package/dist/core/config.js +1379 -0
- package/dist/core/daily-memory-promotion.d.ts +21 -0
- package/dist/core/daily-memory-promotion.js +422 -0
- package/dist/core/i18n.d.ts +23 -0
- package/dist/core/i18n.js +167 -0
- package/dist/core/info-lines.d.ts +5 -0
- package/dist/core/info-lines.js +39 -0
- package/dist/core/input-schema.d.ts +2 -0
- package/dist/core/input-schema.js +156 -0
- package/dist/core/intent-router.d.ts +27 -0
- package/dist/core/intent-router.js +160 -0
- package/dist/core/logger.d.ts +60 -0
- package/dist/core/logger.js +240 -0
- package/dist/core/memory.d.ts +10 -0
- package/dist/core/memory.js +72 -0
- package/dist/core/message-utils.d.ts +13 -0
- package/dist/core/message-utils.js +104 -0
- package/dist/core/notifier.d.ts +17 -0
- package/dist/core/notifier.js +424 -0
- package/dist/core/output-writer.d.ts +13 -0
- package/dist/core/output-writer.js +100 -0
- package/dist/core/plan-decision.d.ts +16 -0
- package/dist/core/plan-decision.js +88 -0
- package/dist/core/profile-memory.d.ts +17 -0
- package/dist/core/profile-memory.js +142 -0
- package/dist/core/runtime.d.ts +50 -0
- package/dist/core/runtime.js +187 -0
- package/dist/core/scheduler.d.ts +28 -0
- package/dist/core/scheduler.js +155 -0
- package/dist/core/secrets.d.ts +31 -0
- package/dist/core/secrets.js +214 -0
- package/dist/core/service.d.ts +35 -0
- package/dist/core/service.js +479 -0
- package/dist/core/skill-planner.d.ts +24 -0
- package/dist/core/skill-planner.js +100 -0
- package/dist/core/skill-registry.d.ts +98 -0
- package/dist/core/skill-registry.js +319 -0
- package/dist/core/skill-scaffold.d.ts +33 -0
- package/dist/core/skill-scaffold.js +256 -0
- package/dist/core/skill-settings.d.ts +11 -0
- package/dist/core/skill-settings.js +63 -0
- package/dist/core/transfer.d.ts +31 -0
- package/dist/core/transfer.js +270 -0
- package/dist/core/update-notifier.d.ts +2 -0
- package/dist/core/update-notifier.js +140 -0
- package/dist/discord/bot.d.ts +96 -0
- package/dist/discord/bot.js +2424 -0
- package/dist/runtimes/codex-app-server.d.ts +53 -0
- package/dist/runtimes/codex-app-server.js +305 -0
- package/dist/runtimes/python-runner.d.ts +7 -0
- package/dist/runtimes/python-runner.js +302 -0
- package/dist/runtimes/typescript-runner.d.ts +5 -0
- package/dist/runtimes/typescript-runner.js +172 -0
- package/dist/skills-sdk/types.d.ts +38 -0
- package/dist/skills-sdk/types.js +1 -0
- package/dist/telegram/bot.d.ts +94 -0
- package/dist/telegram/bot.js +1219 -0
- package/install.ps1 +132 -0
- package/install.sh +130 -0
- package/package.json +60 -0
- package/templates/skill-python/main.py +74 -0
- package/templates/skill-python/skill.yaml +48 -0
- package/templates/skill-typescript/main.ts +87 -0
- 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
|